@webspire/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/data/registry.json +24522 -0
- package/dist/handlers.d.ts +4 -0
- package/dist/handlers.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +20 -0
- package/dist/registration.d.ts +6 -0
- package/dist/registration.js +471 -0
- package/dist/registry.d.ts +17 -0
- package/dist/registry.js +60 -0
- package/dist/search.d.ts +12 -0
- package/dist/search.js +65 -0
- package/dist/tools.d.ts +3 -0
- package/dist/tools.js +151 -0
- package/dist/types.d.ts +114 -0
- package/dist/types.js +1 -0
- package/package.json +56 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { loadRegistry, searchSnippets, suggestSnippets, } from './registry.js';
|
|
3
|
+
function formatSnippetBrief(s) {
|
|
4
|
+
return [
|
|
5
|
+
`**${s.title}** (${s.id})`,
|
|
6
|
+
` ${s.description}`,
|
|
7
|
+
` Category: ${s.category} | Tags: ${s.tags.join(', ')}`,
|
|
8
|
+
s.meta.useCases.length > 0
|
|
9
|
+
? ` Use cases: ${s.meta.useCases.slice(0, 2).join('; ')}`
|
|
10
|
+
: '',
|
|
11
|
+
]
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
.join('\n');
|
|
14
|
+
}
|
|
15
|
+
function formatSnippetFull(s) {
|
|
16
|
+
const props = s.meta.customProperties
|
|
17
|
+
.map((p) => ` ${p}: ${s.meta.defaultValues[p] ?? 'unset'}`)
|
|
18
|
+
.join('\n');
|
|
19
|
+
return [
|
|
20
|
+
`# ${s.title}`,
|
|
21
|
+
'',
|
|
22
|
+
s.description,
|
|
23
|
+
'',
|
|
24
|
+
'## Usage',
|
|
25
|
+
'```html',
|
|
26
|
+
s.meta.usageExample,
|
|
27
|
+
'```',
|
|
28
|
+
'',
|
|
29
|
+
'## CSS',
|
|
30
|
+
'```css',
|
|
31
|
+
s.css,
|
|
32
|
+
'```',
|
|
33
|
+
'',
|
|
34
|
+
s.meta.customProperties.length > 0 ? `## Custom Properties\n${props}` : '',
|
|
35
|
+
s.meta.classes.length > 0
|
|
36
|
+
? `## Classes\n${s.meta.classes.join(', ')}`
|
|
37
|
+
: '',
|
|
38
|
+
s.meta.accessibility.length > 0
|
|
39
|
+
? `## Accessibility\nRespects: ${s.meta.accessibility.join(', ')}`
|
|
40
|
+
: '',
|
|
41
|
+
'',
|
|
42
|
+
`Browser support: ${s.meta.browser}`,
|
|
43
|
+
`Size: ${s.meta.lines} lines, ${s.meta.bytes} bytes`,
|
|
44
|
+
]
|
|
45
|
+
.filter((line) => line !== '')
|
|
46
|
+
.join('\n');
|
|
47
|
+
}
|
|
48
|
+
export function registerTools(server, registryOptions) {
|
|
49
|
+
server.tool('list_categories', 'List all available snippet categories with snippet counts', {}, async () => {
|
|
50
|
+
const registry = await loadRegistry(registryOptions);
|
|
51
|
+
const counts = {};
|
|
52
|
+
for (const snippet of registry.snippets) {
|
|
53
|
+
counts[snippet.category] = (counts[snippet.category] ?? 0) + 1;
|
|
54
|
+
}
|
|
55
|
+
const result = Object.entries(counts)
|
|
56
|
+
.sort(([, a], [, b]) => b - a)
|
|
57
|
+
.map(([cat, count]) => `${cat}: ${count} snippet${count > 1 ? 's' : ''}`)
|
|
58
|
+
.join('\n');
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: 'text',
|
|
63
|
+
text: `${registry.snippets.length} snippets across ${Object.keys(counts).length} categories:\n\n${result}`,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
server.tool('search_snippets', 'Search Webspire CSS snippets by keyword, problem description, or use case. Returns matching snippets with their descriptions and IDs.', {
|
|
69
|
+
query: z
|
|
70
|
+
.string()
|
|
71
|
+
.describe('Search query: problem description, use case, or CSS technique (e.g. "glass card", "fade animation", "blur entrance")'),
|
|
72
|
+
category: z
|
|
73
|
+
.string()
|
|
74
|
+
.optional()
|
|
75
|
+
.describe('Filter by category: glass, animations, easing, scroll, decorative, interactions, text'),
|
|
76
|
+
tags: z.array(z.string()).optional().describe('Filter by tags'),
|
|
77
|
+
}, async ({ query, category, tags }) => {
|
|
78
|
+
const registry = await loadRegistry(registryOptions);
|
|
79
|
+
const results = searchSnippets(registry, query, category, tags);
|
|
80
|
+
if (results.length === 0) {
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: 'text',
|
|
85
|
+
text: 'No snippets found matching your query. Try broader keywords or check available categories with list_categories.',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const text = results.map(formatSnippetBrief).join('\n\n');
|
|
91
|
+
return {
|
|
92
|
+
content: [
|
|
93
|
+
{
|
|
94
|
+
type: 'text',
|
|
95
|
+
text: `Found ${results.length} snippet${results.length > 1 ? 's' : ''}:\n\n${text}`,
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
server.tool('get_snippet', 'Get full CSS source, metadata, usage example, and custom properties for a specific snippet. Use this after finding a snippet via search_snippets or suggest_snippet.', {
|
|
101
|
+
id: z
|
|
102
|
+
.string()
|
|
103
|
+
.describe('Snippet ID, e.g. "glass/frosted", "animations/fade-in", "easing/tokens"'),
|
|
104
|
+
}, async ({ id }) => {
|
|
105
|
+
const registry = await loadRegistry(registryOptions);
|
|
106
|
+
const snippet = registry.snippets.find((s) => s.id === id);
|
|
107
|
+
if (!snippet) {
|
|
108
|
+
const available = registry.snippets.map((s) => s.id).join(', ');
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: 'text',
|
|
113
|
+
text: `Snippet "${id}" not found. Available snippets: ${available}`,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: 'text', text: formatSnippetFull(snippet) }],
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
server.tool('suggest_snippet', 'Describe a UI problem or desired effect in plain language and get the best matching Webspire snippets. The AI matches your description against each snippet\'s use cases and problem statements.', {
|
|
123
|
+
problem: z
|
|
124
|
+
.string()
|
|
125
|
+
.describe('Describe the UI problem or effect you need (e.g. "I need a blurred card overlay on my hero section", "my list items should animate in one by one")'),
|
|
126
|
+
}, async ({ problem }) => {
|
|
127
|
+
const registry = await loadRegistry(registryOptions);
|
|
128
|
+
const results = suggestSnippets(registry, problem);
|
|
129
|
+
if (results.length === 0) {
|
|
130
|
+
return {
|
|
131
|
+
content: [
|
|
132
|
+
{
|
|
133
|
+
type: 'text',
|
|
134
|
+
text: 'No matching snippet found for your description. Try rephrasing or use search_snippets with specific keywords.',
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const text = results
|
|
140
|
+
.map((s, i) => `${i + 1}. ${formatSnippetBrief(s)}\n Usage: \`${s.meta.usageExample}\``)
|
|
141
|
+
.join('\n\n');
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: 'text',
|
|
146
|
+
text: `Top ${results.length} suggestion${results.length > 1 ? 's' : ''} for "${problem}":\n\n${text}\n\nUse get_snippet(id) for full CSS and details.`,
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export type SnippetCategory = 'glass' | 'animations' | 'easing' | 'scroll' | 'decorative' | 'interactions' | 'text';
|
|
2
|
+
export interface SnippetAccessibility {
|
|
3
|
+
prefersReducedMotion: boolean;
|
|
4
|
+
prefersColorScheme: boolean;
|
|
5
|
+
supportsCheck: boolean;
|
|
6
|
+
ariaNotes?: string[];
|
|
7
|
+
contrastNotes?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface SnippetMeta {
|
|
10
|
+
lines: number;
|
|
11
|
+
bytes: number;
|
|
12
|
+
tailwind: string;
|
|
13
|
+
browser: string;
|
|
14
|
+
accessibility: SnippetAccessibility;
|
|
15
|
+
customProperties: string[];
|
|
16
|
+
classes: string[];
|
|
17
|
+
useCases: string[];
|
|
18
|
+
solves: string[];
|
|
19
|
+
usageExample: string;
|
|
20
|
+
defaultValues?: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
export interface SnippetEntry {
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
description: string;
|
|
26
|
+
category: SnippetCategory;
|
|
27
|
+
tags: string[];
|
|
28
|
+
responsive: boolean;
|
|
29
|
+
darkMode: boolean;
|
|
30
|
+
dependencies?: string[];
|
|
31
|
+
variants?: string[];
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
meta: SnippetMeta;
|
|
34
|
+
css: string;
|
|
35
|
+
}
|
|
36
|
+
export interface PatternEntry {
|
|
37
|
+
id: string;
|
|
38
|
+
title: string;
|
|
39
|
+
summary: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
family: string;
|
|
42
|
+
kind: 'section' | 'component' | 'pattern' | 'layout';
|
|
43
|
+
tier: 'base' | 'enhanced';
|
|
44
|
+
extends: string | null;
|
|
45
|
+
tags: string[];
|
|
46
|
+
search: {
|
|
47
|
+
intent: string[];
|
|
48
|
+
keywords: string[];
|
|
49
|
+
useCases: string[];
|
|
50
|
+
};
|
|
51
|
+
frameworks: {
|
|
52
|
+
html: boolean;
|
|
53
|
+
astro: boolean;
|
|
54
|
+
webComponent: boolean;
|
|
55
|
+
};
|
|
56
|
+
tokens: {
|
|
57
|
+
color: string;
|
|
58
|
+
radius: string;
|
|
59
|
+
shadow: string;
|
|
60
|
+
spacing: string;
|
|
61
|
+
typography: string;
|
|
62
|
+
};
|
|
63
|
+
capabilities: {
|
|
64
|
+
responsive: boolean;
|
|
65
|
+
darkMode: boolean;
|
|
66
|
+
animated: boolean;
|
|
67
|
+
interactive: boolean;
|
|
68
|
+
dependencies: string[];
|
|
69
|
+
animationPreset: 'none' | 'fade-up' | 'fade-in' | 'scale-in' | 'slide-up' | 'stagger-children' | null;
|
|
70
|
+
};
|
|
71
|
+
slots: Array<{
|
|
72
|
+
name: string;
|
|
73
|
+
required: boolean;
|
|
74
|
+
description: string;
|
|
75
|
+
}>;
|
|
76
|
+
props: Array<{
|
|
77
|
+
name: string;
|
|
78
|
+
type: string;
|
|
79
|
+
default: unknown;
|
|
80
|
+
description: string;
|
|
81
|
+
}>;
|
|
82
|
+
files: {
|
|
83
|
+
html: string;
|
|
84
|
+
css: string | null;
|
|
85
|
+
js: string | null;
|
|
86
|
+
astro?: string;
|
|
87
|
+
webComponent?: string;
|
|
88
|
+
};
|
|
89
|
+
install: {
|
|
90
|
+
copyPasteReady: boolean;
|
|
91
|
+
ssrSafe: boolean;
|
|
92
|
+
tailwindOnly: boolean;
|
|
93
|
+
notes: string[];
|
|
94
|
+
};
|
|
95
|
+
ai?: {
|
|
96
|
+
prompts: string[];
|
|
97
|
+
compositionRole: 'entry' | 'supporting' | 'navigation' | 'conversion' | 'trust' | 'footer';
|
|
98
|
+
};
|
|
99
|
+
governance: {
|
|
100
|
+
status: 'draft' | 'review' | 'published' | 'deprecated';
|
|
101
|
+
quality: 'experimental' | 'stable' | 'flagship';
|
|
102
|
+
owner: string;
|
|
103
|
+
updatedAt: string;
|
|
104
|
+
};
|
|
105
|
+
html: string;
|
|
106
|
+
css: string | null;
|
|
107
|
+
js: string | null;
|
|
108
|
+
}
|
|
109
|
+
export interface Registry {
|
|
110
|
+
version: string;
|
|
111
|
+
generated: string;
|
|
112
|
+
snippets: SnippetEntry[];
|
|
113
|
+
patterns?: PatternEntry[];
|
|
114
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webspire/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Webspire CSS snippets — AI-native snippet discovery and matching",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js",
|
|
8
|
+
"./handlers": "./dist/handlers.js",
|
|
9
|
+
"./registry": "./dist/registry.js",
|
|
10
|
+
"./search": "./dist/search.js",
|
|
11
|
+
"./registration": "./dist/registration.js"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"webspire-mcp": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc && node scripts/bundle-registry.mjs",
|
|
18
|
+
"dev": "tsx src/index.ts",
|
|
19
|
+
"clean": "rm -rf dist"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"data"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "catalog:",
|
|
27
|
+
"zod": "catalog:"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.0.0",
|
|
31
|
+
"tsx": "catalog:",
|
|
32
|
+
"typescript": "catalog:"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"mcp",
|
|
36
|
+
"model-context-protocol",
|
|
37
|
+
"css",
|
|
38
|
+
"snippets",
|
|
39
|
+
"ui-patterns",
|
|
40
|
+
"tailwind",
|
|
41
|
+
"ai",
|
|
42
|
+
"ai-native",
|
|
43
|
+
"glassmorphism",
|
|
44
|
+
"component-registry"
|
|
45
|
+
],
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/casoon/webspire",
|
|
49
|
+
"directory": "packages/mcp"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://webspire.de",
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
}
|
|
56
|
+
}
|