@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 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
@@ -11,7 +11,7 @@ const registryOptions = {
11
11
  };
12
12
  const server = new McpServer({
13
13
  name: 'webspire',
14
- version: '0.1.0',
14
+ version: '0.2.0',
15
15
  });
16
16
  registerTools(server, registryOptions);
17
17
  registerResources(server, registryOptions);
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, stems: string[]): number;
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
- 'ness',
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
- export function scoreSnippet(snippet, stems) {
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 += stems.filter((w) => lower.includes(w)).length * 3;
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 += stems.filter((w) => lower.includes(w)).length * 2;
82
+ score += queryStems.filter((w) => lower.includes(w)).length * 3;
32
83
  }
33
- const desc = snippet.description.toLowerCase();
34
- score += stems.filter((w) => desc.includes(w)).length;
84
+ // Title important
35
85
  const title = snippet.title.toLowerCase();
36
- score += stems.filter((w) => title.includes(w)).length * 2;
37
- score += stems.filter((w) => snippet.tags.some((t) => t.includes(w))).length;
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 += stems.filter((w) => classes.includes(w)).length;
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.1.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": "catalog:",
27
- "zod": "catalog:"
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": "catalog:",
32
- "typescript": "catalog:"
31
+ "tsx": "^4.21.0",
32
+ "typescript": "^5.9.3"
33
33
  },
34
34
  "keywords": [
35
35
  "mcp",