@webspire/mcp 0.1.0 → 0.2.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 +92 -0
- package/dist/index.js +1 -1
- package/dist/search.d.ts +1 -1
- package/dist/search.js +77 -12
- package/package.json +5 -5
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# @webspire/mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [Webspire](https://webspire.de) CSS snippets — AI-native snippet discovery and matching.
|
|
4
|
+
|
|
5
|
+
49 production-ready CSS snippets across 7 categories: glass, animations, easing, scroll, decorative, interactions, text.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
### Claude Code / Claude Desktop
|
|
10
|
+
|
|
11
|
+
Add to your MCP settings (`~/.claude/settings.json` or Claude Desktop config):
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"webspire": {
|
|
17
|
+
"command": "npx",
|
|
18
|
+
"args": ["-y", "@webspire/mcp"]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Custom Registry
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"webspire": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "@webspire/mcp", "--registry-url", "https://your-domain.com/registry.json"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Available Tools
|
|
38
|
+
|
|
39
|
+
| Tool | Description |
|
|
40
|
+
|------|-------------|
|
|
41
|
+
| `list_categories` | List all snippet categories with counts |
|
|
42
|
+
| `search_snippets` | Search by keyword, use case, or CSS technique |
|
|
43
|
+
| `get_snippet` | Get full CSS source, metadata, and usage example |
|
|
44
|
+
| `recommend_snippet` | Describe a UI problem, get best matching snippets |
|
|
45
|
+
| `list_pattern_families` | List all UI pattern families |
|
|
46
|
+
| `search_patterns` | Search UI patterns by intent |
|
|
47
|
+
| `get_pattern` | Get full pattern HTML/CSS/JS |
|
|
48
|
+
|
|
49
|
+
## Available Resources
|
|
50
|
+
|
|
51
|
+
| URI | Description |
|
|
52
|
+
|-----|-------------|
|
|
53
|
+
| `webspire://categories` | All categories with counts (JSON) |
|
|
54
|
+
| `webspire://category/{name}` | Snippets in a category (JSON) |
|
|
55
|
+
| `webspire://snippet/{id}` | Full snippet data including CSS (JSON) |
|
|
56
|
+
| `webspire://patterns` | All patterns (JSON) |
|
|
57
|
+
| `webspire://pattern/{id}` | Full pattern data (JSON) |
|
|
58
|
+
|
|
59
|
+
## Snippet Categories
|
|
60
|
+
|
|
61
|
+
- **glass** — Frosted, subtle, bold, colored, dark glassmorphism effects
|
|
62
|
+
- **animations** — Fade, slide, scale, blur, stagger, pulse, infinite scroll
|
|
63
|
+
- **easing** — Spring, bounce, snap easing tokens
|
|
64
|
+
- **scroll** — Parallax, reveal, progress bar, snap, sticky header
|
|
65
|
+
- **decorative** — Aurora, gradient mesh, neon glow, noise, orbs, shimmer
|
|
66
|
+
- **interactions** — Hover lift, ripple, flip card, tilt, magnetic, focus ring
|
|
67
|
+
- **text** — Fluid size, gradient text, typewriter, truncate, balance
|
|
68
|
+
|
|
69
|
+
## Search Features
|
|
70
|
+
|
|
71
|
+
- **Synonym expansion**: "blur" also finds glass/frosted snippets
|
|
72
|
+
- **Stemming**: "animations" matches "animated", "animate"
|
|
73
|
+
- **Multi-field scoring**: Matches in title, description, use cases, tags, classes
|
|
74
|
+
- **Filters**: Category, tags, dark mode support, responsive support
|
|
75
|
+
|
|
76
|
+
## Programmatic Usage
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { loadRegistry, searchSnippets } from '@webspire/mcp/registry';
|
|
80
|
+
|
|
81
|
+
const registry = await loadRegistry();
|
|
82
|
+
const results = searchSnippets(registry, {
|
|
83
|
+
query: 'glass card hover',
|
|
84
|
+
category: 'glass',
|
|
85
|
+
darkMode: true,
|
|
86
|
+
limit: 5,
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
package/dist/index.js
CHANGED
package/dist/search.d.ts
CHANGED
|
@@ -8,5 +8,5 @@ export interface SnippetSearchOptions {
|
|
|
8
8
|
limit?: number;
|
|
9
9
|
}
|
|
10
10
|
export declare function stem(word: string): string;
|
|
11
|
-
export declare function scoreSnippet(snippet: SnippetEntry,
|
|
11
|
+
export declare function scoreSnippet(snippet: SnippetEntry, queryStems: string[], expandedTerms: string[]): number;
|
|
12
12
|
export declare function searchSnippets(snippets: SnippetEntry[], options: SnippetSearchOptions): SnippetEntry[];
|
package/dist/search.js
CHANGED
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
const STEM_SUFFIXES = [
|
|
2
|
-
'ions',
|
|
3
|
-
'ing',
|
|
4
2
|
'tion',
|
|
5
|
-
'
|
|
3
|
+
'sion',
|
|
4
|
+
'ions',
|
|
6
5
|
'ment',
|
|
6
|
+
'ness',
|
|
7
7
|
'able',
|
|
8
8
|
'ible',
|
|
9
9
|
'ous',
|
|
10
10
|
'ive',
|
|
11
|
+
'ing',
|
|
11
12
|
'ed',
|
|
12
13
|
'es',
|
|
14
|
+
'ly',
|
|
13
15
|
's',
|
|
14
16
|
];
|
|
17
|
+
/** Common synonyms for CSS/UI terms — maps alternative words to canonical terms */
|
|
18
|
+
const SYNONYMS = {
|
|
19
|
+
blur: ['glass', 'frosted', 'glassmorphism', 'backdrop'],
|
|
20
|
+
glass: ['blur', 'frosted', 'glassmorphism', 'transparent'],
|
|
21
|
+
fade: ['opacity', 'appear', 'entrance', 'reveal'],
|
|
22
|
+
slide: ['translate', 'move', 'enter', 'entrance'],
|
|
23
|
+
hover: ['mouse', 'interaction', 'lift', 'pointer'],
|
|
24
|
+
card: ['panel', 'surface', 'container', 'box'],
|
|
25
|
+
animation: ['animate', 'motion', 'transition', 'keyframe'],
|
|
26
|
+
glow: ['neon', 'light', 'shine', 'luminous'],
|
|
27
|
+
scroll: ['viewport', 'intersection', 'parallax', 'progress'],
|
|
28
|
+
text: ['font', 'typography', 'heading', 'title'],
|
|
29
|
+
gradient: ['color', 'blend', 'mesh', 'aurora'],
|
|
30
|
+
dark: ['darkmode', 'theme', 'scheme', 'night'],
|
|
31
|
+
button: ['btn', 'cta', 'action', 'click'],
|
|
32
|
+
responsive: ['mobile', 'fluid', 'adaptive', 'clamp'],
|
|
33
|
+
focus: ['keyboard', 'a11y', 'accessibility', 'ring'],
|
|
34
|
+
stagger: ['cascade', 'sequence', 'delay', 'children'],
|
|
35
|
+
reveal: ['show', 'appear', 'unhide', 'visible', 'fade'],
|
|
36
|
+
shimmer: ['skeleton', 'loading', 'placeholder', 'pulse'],
|
|
37
|
+
ripple: ['click', 'material', 'touch', 'feedback'],
|
|
38
|
+
};
|
|
15
39
|
export function stem(word) {
|
|
16
40
|
for (const suffix of STEM_SUFFIXES) {
|
|
17
41
|
if (word.length > suffix.length + 2 && word.endsWith(suffix)) {
|
|
@@ -20,29 +44,70 @@ export function stem(word) {
|
|
|
20
44
|
}
|
|
21
45
|
return word;
|
|
22
46
|
}
|
|
23
|
-
|
|
47
|
+
/** Expand query terms with synonyms for broader matching */
|
|
48
|
+
function expandWithSynonyms(terms) {
|
|
49
|
+
const expanded = new Set(terms);
|
|
50
|
+
for (const term of terms) {
|
|
51
|
+
// Direct synonym lookup
|
|
52
|
+
if (SYNONYMS[term]) {
|
|
53
|
+
for (const syn of SYNONYMS[term]) {
|
|
54
|
+
expanded.add(syn);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Reverse lookup: if term appears as a synonym value
|
|
58
|
+
for (const [key, values] of Object.entries(SYNONYMS)) {
|
|
59
|
+
if (values.includes(term)) {
|
|
60
|
+
expanded.add(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return [...expanded];
|
|
65
|
+
}
|
|
66
|
+
export function scoreSnippet(snippet, queryStems, expandedTerms) {
|
|
24
67
|
let score = 0;
|
|
68
|
+
// Exact ID match — highest priority
|
|
69
|
+
const idLower = snippet.id.toLowerCase();
|
|
70
|
+
for (const term of queryStems) {
|
|
71
|
+
if (idLower.includes(term))
|
|
72
|
+
score += 5;
|
|
73
|
+
}
|
|
74
|
+
// solves field — primary intent match
|
|
25
75
|
for (const solve of snippet.meta.solves) {
|
|
26
76
|
const lower = solve.toLowerCase();
|
|
27
|
-
score +=
|
|
77
|
+
score += queryStems.filter((w) => lower.includes(w)).length * 4;
|
|
28
78
|
}
|
|
79
|
+
// useCases — strong signal
|
|
29
80
|
for (const useCase of snippet.meta.useCases) {
|
|
30
81
|
const lower = useCase.toLowerCase();
|
|
31
|
-
score +=
|
|
82
|
+
score += queryStems.filter((w) => lower.includes(w)).length * 3;
|
|
32
83
|
}
|
|
33
|
-
|
|
34
|
-
score += stems.filter((w) => desc.includes(w)).length;
|
|
84
|
+
// Title — important
|
|
35
85
|
const title = snippet.title.toLowerCase();
|
|
36
|
-
score +=
|
|
37
|
-
|
|
86
|
+
score += queryStems.filter((w) => title.includes(w)).length * 3;
|
|
87
|
+
// Description
|
|
88
|
+
const desc = snippet.description.toLowerCase();
|
|
89
|
+
score += queryStems.filter((w) => desc.includes(w)).length * 2;
|
|
90
|
+
// Tags
|
|
91
|
+
const tagStr = snippet.tags.join(' ').toLowerCase();
|
|
92
|
+
score += queryStems.filter((w) => tagStr.includes(w)).length * 2;
|
|
93
|
+
// Classes
|
|
38
94
|
const classes = snippet.meta.classes.join(' ').toLowerCase();
|
|
39
|
-
score +=
|
|
95
|
+
score += queryStems.filter((w) => classes.includes(w)).length;
|
|
96
|
+
// Synonym-expanded terms — lower weight to avoid noise
|
|
97
|
+
const synonymOnly = expandedTerms.filter((t) => !queryStems.includes(t));
|
|
98
|
+
if (synonymOnly.length > 0) {
|
|
99
|
+
const allText = [title, desc, tagStr, ...snippet.meta.useCases, ...snippet.meta.solves]
|
|
100
|
+
.join(' ')
|
|
101
|
+
.toLowerCase();
|
|
102
|
+
score += synonymOnly.filter((w) => allText.includes(w)).length;
|
|
103
|
+
}
|
|
40
104
|
return score;
|
|
41
105
|
}
|
|
42
106
|
export function searchSnippets(snippets, options) {
|
|
43
107
|
const { query, category, tags, darkMode, responsive, limit = 10 } = options;
|
|
44
108
|
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
45
109
|
const stems = words.map(stem);
|
|
110
|
+
const expanded = expandWithSynonyms(words);
|
|
46
111
|
if (stems.length === 0) {
|
|
47
112
|
return [];
|
|
48
113
|
}
|
|
@@ -58,7 +123,7 @@ export function searchSnippets(snippets, options) {
|
|
|
58
123
|
return false;
|
|
59
124
|
return true;
|
|
60
125
|
})
|
|
61
|
-
.map((snippet) => ({ snippet, score: scoreSnippet(snippet, stems) }))
|
|
126
|
+
.map((snippet) => ({ snippet, score: scoreSnippet(snippet, stems, expanded) }))
|
|
62
127
|
.filter(({ score }) => score > 0)
|
|
63
128
|
.sort((a, b) => b.score - a.score);
|
|
64
129
|
return scored.slice(0, limit).map(({ snippet }) => snippet);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webspire/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for Webspire CSS snippets — AI-native snippet discovery and matching",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"data"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@modelcontextprotocol/sdk": "
|
|
27
|
-
"zod": "
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.13.0",
|
|
27
|
+
"zod": "^4.2.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^22.0.0",
|
|
31
|
-
"tsx": "
|
|
32
|
-
"typescript": "
|
|
31
|
+
"tsx": "^4.21.0",
|
|
32
|
+
"typescript": "^5.9.3"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
35
35
|
"mcp",
|