seedflip-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/README.md +79 -0
- package/dist/exporters.d.ts +11 -0
- package/dist/exporters.js +238 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +122 -0
- package/dist/search.d.ts +45 -0
- package/dist/search.js +187 -0
- package/dist/seeds-data.json +3562 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# SeedFlip MCP Server
|
|
2
|
+
|
|
3
|
+
Gives AI agents access to 100+ curated design systems. When an agent needs a design direction, it calls SeedFlip instead of guessing colors and fonts.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### Claude Desktop
|
|
8
|
+
|
|
9
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"seedflip": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["tsx", "/Users/LUKE/design-shuffle/mcp/src/index.ts"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Claude Code
|
|
23
|
+
|
|
24
|
+
Add to `.claude/settings.json` or run:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
claude mcp add seedflip -- npx tsx /Users/LUKE/design-shuffle/mcp/src/index.ts
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Cursor
|
|
31
|
+
|
|
32
|
+
Add to `.cursor/mcp.json`:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"seedflip": {
|
|
38
|
+
"command": "npx",
|
|
39
|
+
"args": ["tsx", "/Users/LUKE/design-shuffle/mcp/src/index.ts"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Tools
|
|
46
|
+
|
|
47
|
+
### `get_design_seed`
|
|
48
|
+
|
|
49
|
+
Get a curated design system by reference, vibe, or style.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
query: "Stripe" → Returns the Stripe-inspired seed
|
|
53
|
+
query: "dark minimal" → Returns a dark, minimal seed
|
|
54
|
+
query: "brutalist" → Returns a brutalist seed
|
|
55
|
+
query: "warm editorial" → Returns a warm editorial seed
|
|
56
|
+
format: "tailwind" → Returns a ready-to-use tailwind.config.ts
|
|
57
|
+
format: "css" → Returns CSS custom properties
|
|
58
|
+
format: "shadcn" → Returns shadcn/ui theme
|
|
59
|
+
format: "tokens" → Returns all values with design brief (default)
|
|
60
|
+
count: 3 → Returns top 3 matches
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `list_design_seeds`
|
|
64
|
+
|
|
65
|
+
Browse available seeds with optional tag filtering.
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
tag: "dark" → All dark mode seeds
|
|
69
|
+
tag: "brutalist" → All brutalist seeds
|
|
70
|
+
tag: "warm" → All warm-toned seeds
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Updating Seeds
|
|
74
|
+
|
|
75
|
+
After adding/changing seeds in `src/lib/seeds.ts`, run:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx tsx mcp/scripts/extract-seeds.mjs
|
|
79
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SeedFlip MCP — Export Generators
|
|
3
|
+
*
|
|
4
|
+
* Produces ready-to-use config files directly from DesignSeed data.
|
|
5
|
+
* All colors are already hex — no conversion needed.
|
|
6
|
+
*/
|
|
7
|
+
import type { DesignSeed } from './search.js';
|
|
8
|
+
export declare function formatTokens(seed: DesignSeed): string;
|
|
9
|
+
export declare function formatTailwind(seed: DesignSeed): string;
|
|
10
|
+
export declare function formatCSS(seed: DesignSeed): string;
|
|
11
|
+
export declare function formatShadcn(seed: DesignSeed): string;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SeedFlip MCP — Export Generators
|
|
3
|
+
*
|
|
4
|
+
* Produces ready-to-use config files directly from DesignSeed data.
|
|
5
|
+
* All colors are already hex — no conversion needed.
|
|
6
|
+
*/
|
|
7
|
+
// ── Helpers ──────────────────────────────────────────────────────
|
|
8
|
+
function fontFamily(name) {
|
|
9
|
+
const mono = name.toLowerCase().includes('mono') ||
|
|
10
|
+
['JetBrains Mono', 'Fira Code', 'IBM Plex Mono', 'Iosevka'].includes(name);
|
|
11
|
+
const serif = [
|
|
12
|
+
'Playfair Display', 'DM Serif Display', 'Fraunces', 'Lora',
|
|
13
|
+
'Merriweather', 'Source Serif 4', 'Crimson Text', 'EB Garamond',
|
|
14
|
+
'Cormorant Garamond', 'Libre Baskerville', 'Bespoke Serif',
|
|
15
|
+
'Bonny', 'Boska', 'Erode', 'Gambetta', 'Neco', 'Recia',
|
|
16
|
+
'Rowan', 'Sentient', 'Zodiak', 'Bitter', 'Spectral',
|
|
17
|
+
'Instrument Serif', 'Noto Serif Display',
|
|
18
|
+
].includes(name);
|
|
19
|
+
const fallback = mono ? 'monospace' : serif ? 'serif' : 'sans-serif';
|
|
20
|
+
return `'${name}', ${fallback}`;
|
|
21
|
+
}
|
|
22
|
+
function googleFontsUrl(seed) {
|
|
23
|
+
const fonts = new Set([seed.headingFont, seed.bodyFont]);
|
|
24
|
+
const families = [...fonts].map((f) => {
|
|
25
|
+
const weights = f === seed.headingFont ? `wght@${seed.headingWeight}` : 'wght@400;500';
|
|
26
|
+
return `family=${f.replace(/ /g, '+')}:${weights}`;
|
|
27
|
+
});
|
|
28
|
+
return `https://fonts.googleapis.com/css2?${families.join('&')}&display=swap`;
|
|
29
|
+
}
|
|
30
|
+
function hexToHSLString(hex) {
|
|
31
|
+
const c = hex.replace('#', '');
|
|
32
|
+
const r = parseInt(c.slice(0, 2), 16) / 255;
|
|
33
|
+
const g = parseInt(c.slice(2, 4), 16) / 255;
|
|
34
|
+
const b = parseInt(c.slice(4, 6), 16) / 255;
|
|
35
|
+
const max = Math.max(r, g, b);
|
|
36
|
+
const min = Math.min(r, g, b);
|
|
37
|
+
const l = (max + min) / 2;
|
|
38
|
+
if (max === min)
|
|
39
|
+
return `0 0% ${Math.round(l * 100)}%`;
|
|
40
|
+
const d = max - min;
|
|
41
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
42
|
+
let h = 0;
|
|
43
|
+
if (max === r)
|
|
44
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
45
|
+
else if (max === g)
|
|
46
|
+
h = ((b - r) / d + 2) / 6;
|
|
47
|
+
else
|
|
48
|
+
h = ((r - g) / d + 4) / 6;
|
|
49
|
+
return `${(h * 360).toFixed(1)} ${(s * 100).toFixed(1)}% ${(l * 100).toFixed(1)}%`;
|
|
50
|
+
}
|
|
51
|
+
function hexLuminance(hex) {
|
|
52
|
+
const c = hex.replace('#', '');
|
|
53
|
+
const r = parseInt(c.substring(0, 2), 16);
|
|
54
|
+
const g = parseInt(c.substring(2, 4), 16);
|
|
55
|
+
const b = parseInt(c.substring(4, 6), 16);
|
|
56
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
57
|
+
}
|
|
58
|
+
// ── Token summary (default format) ──────────────────────────────
|
|
59
|
+
export function formatTokens(seed) {
|
|
60
|
+
const isDark = hexLuminance(seed.bg) < 0.5;
|
|
61
|
+
return `## SeedFlip — ${seed.name}
|
|
62
|
+
|
|
63
|
+
**${seed.vibe}**
|
|
64
|
+
Mode: ${isDark ? 'Dark' : 'Light'} | Tags: ${seed.tags.join(', ')}
|
|
65
|
+
|
|
66
|
+
### Fonts
|
|
67
|
+
- Heading: ${seed.headingFont} (weight: ${seed.headingWeight}, spacing: ${seed.letterSpacing})
|
|
68
|
+
- Body: ${seed.bodyFont}
|
|
69
|
+
- CSS: font-family: ${fontFamily(seed.headingFont)}
|
|
70
|
+
- Import: ${googleFontsUrl(seed)}
|
|
71
|
+
|
|
72
|
+
### Colors
|
|
73
|
+
| Token | Value |
|
|
74
|
+
|-------|-------|
|
|
75
|
+
| Background | ${seed.bg} |
|
|
76
|
+
| Surface | ${seed.surface} |
|
|
77
|
+
| Surface Hover | ${seed.surfaceHover} |
|
|
78
|
+
| Border | ${seed.border} |
|
|
79
|
+
| Text | ${seed.text} |
|
|
80
|
+
| Text Muted | ${seed.textMuted} |
|
|
81
|
+
| Accent | ${seed.accent} |
|
|
82
|
+
| Accent Soft | ${seed.accentSoft} |
|
|
83
|
+
|
|
84
|
+
### Shape
|
|
85
|
+
- Border Radius: ${seed.radius} (sm: ${seed.radiusSm}, xl: ${seed.radiusXl})
|
|
86
|
+
- Shadow: ${seed.shadow}
|
|
87
|
+
- Shadow Style: ${seed.shadowStyle}
|
|
88
|
+
- Gradient: ${seed.gradient}
|
|
89
|
+
|
|
90
|
+
### Design Brief
|
|
91
|
+
${seed.aiPromptRules}
|
|
92
|
+
|
|
93
|
+
#### Typography Notes
|
|
94
|
+
${seed.aiPromptTypography}
|
|
95
|
+
|
|
96
|
+
#### Color Notes
|
|
97
|
+
${seed.aiPromptColors}
|
|
98
|
+
|
|
99
|
+
#### Shape Notes
|
|
100
|
+
${seed.aiPromptShape}
|
|
101
|
+
|
|
102
|
+
#### Depth Notes
|
|
103
|
+
${seed.aiPromptDepth}`;
|
|
104
|
+
}
|
|
105
|
+
// ── Tailwind config ─────────────────────────────────────────────
|
|
106
|
+
export function formatTailwind(seed) {
|
|
107
|
+
const headingFallback = fontFamily(seed.headingFont).split(', ')[1];
|
|
108
|
+
const bodyFallback = fontFamily(seed.bodyFont).split(', ')[1];
|
|
109
|
+
return `// SeedFlip — ${seed.name}
|
|
110
|
+
// ${seed.vibe}
|
|
111
|
+
// https://seedflip.com
|
|
112
|
+
|
|
113
|
+
import type { Config } from "tailwindcss";
|
|
114
|
+
|
|
115
|
+
export default {
|
|
116
|
+
theme: {
|
|
117
|
+
extend: {
|
|
118
|
+
colors: {
|
|
119
|
+
background: "${seed.bg}",
|
|
120
|
+
surface: "${seed.surface}",
|
|
121
|
+
"surface-hover": "${seed.surfaceHover}",
|
|
122
|
+
border: "${seed.border}",
|
|
123
|
+
foreground: "${seed.text}",
|
|
124
|
+
muted: "${seed.textMuted}",
|
|
125
|
+
accent: "${seed.accent}",
|
|
126
|
+
"accent-soft": "${seed.accentSoft}",
|
|
127
|
+
},
|
|
128
|
+
borderRadius: {
|
|
129
|
+
DEFAULT: "${seed.radius}",
|
|
130
|
+
sm: "${seed.radiusSm}",
|
|
131
|
+
xl: "${seed.radiusXl}",
|
|
132
|
+
},
|
|
133
|
+
fontFamily: {
|
|
134
|
+
heading: ["${seed.headingFont}", "${headingFallback}"],
|
|
135
|
+
body: ["${seed.bodyFont}", "${bodyFallback}"],
|
|
136
|
+
},
|
|
137
|
+
boxShadow: {
|
|
138
|
+
DEFAULT: "${seed.shadow}",
|
|
139
|
+
sm: "${seed.shadowSm}",
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
} satisfies Config;
|
|
144
|
+
|
|
145
|
+
/*
|
|
146
|
+
Font import — add to your layout or globals.css:
|
|
147
|
+
@import url('${googleFontsUrl(seed)}');
|
|
148
|
+
|
|
149
|
+
Design brief:
|
|
150
|
+
${seed.aiPromptRules}
|
|
151
|
+
*/`;
|
|
152
|
+
}
|
|
153
|
+
// ── CSS custom properties ───────────────────────────────────────
|
|
154
|
+
export function formatCSS(seed) {
|
|
155
|
+
return `/* SeedFlip — ${seed.name} */
|
|
156
|
+
/* ${seed.vibe} */
|
|
157
|
+
@import url('${googleFontsUrl(seed)}');
|
|
158
|
+
|
|
159
|
+
:root {
|
|
160
|
+
/* Colors */
|
|
161
|
+
--sf-bg: ${seed.bg};
|
|
162
|
+
--sf-surface: ${seed.surface};
|
|
163
|
+
--sf-surface-hover: ${seed.surfaceHover};
|
|
164
|
+
--sf-border: ${seed.border};
|
|
165
|
+
--sf-text: ${seed.text};
|
|
166
|
+
--sf-text-muted: ${seed.textMuted};
|
|
167
|
+
--sf-accent: ${seed.accent};
|
|
168
|
+
--sf-accent-soft: ${seed.accentSoft};
|
|
169
|
+
|
|
170
|
+
/* Typography */
|
|
171
|
+
--sf-font-heading: ${fontFamily(seed.headingFont)};
|
|
172
|
+
--sf-font-body: ${fontFamily(seed.bodyFont)};
|
|
173
|
+
--sf-heading-weight: ${seed.headingWeight};
|
|
174
|
+
--sf-letter-spacing: ${seed.letterSpacing};
|
|
175
|
+
|
|
176
|
+
/* Shape */
|
|
177
|
+
--sf-radius: ${seed.radius};
|
|
178
|
+
--sf-radius-sm: ${seed.radiusSm};
|
|
179
|
+
--sf-radius-xl: ${seed.radiusXl};
|
|
180
|
+
|
|
181
|
+
/* Depth */
|
|
182
|
+
--sf-shadow: ${seed.shadow};
|
|
183
|
+
--sf-shadow-sm: ${seed.shadowSm};
|
|
184
|
+
--sf-gradient: ${seed.gradient};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/*
|
|
188
|
+
Design brief:
|
|
189
|
+
${seed.aiPromptRules}
|
|
190
|
+
*/`;
|
|
191
|
+
}
|
|
192
|
+
// ── shadcn/ui theme ─────────────────────────────────────────────
|
|
193
|
+
export function formatShadcn(seed) {
|
|
194
|
+
const bg = hexToHSLString(seed.bg);
|
|
195
|
+
const surface = hexToHSLString(seed.surface);
|
|
196
|
+
const text = hexToHSLString(seed.text);
|
|
197
|
+
const textMuted = hexToHSLString(seed.textMuted);
|
|
198
|
+
const accent = hexToHSLString(seed.accent);
|
|
199
|
+
const border = hexToHSLString(seed.border);
|
|
200
|
+
const primaryFg = hexLuminance(seed.accent) > 0.5 ? '0 0% 9%' : '0 0% 98%';
|
|
201
|
+
const accentFg = hexLuminance(seed.surface) > 0.5 ? '0 0% 9%' : '0 0% 98%';
|
|
202
|
+
const radiusRem = (parseFloat(seed.radius) / 16).toFixed(3).replace(/0+$/, '').replace(/\.$/, '') + 'rem';
|
|
203
|
+
return `/* SeedFlip — ${seed.name} — shadcn/ui theme */
|
|
204
|
+
/* ${seed.vibe} */
|
|
205
|
+
@import url('${googleFontsUrl(seed)}');
|
|
206
|
+
|
|
207
|
+
@layer base {
|
|
208
|
+
:root {
|
|
209
|
+
--background: ${bg};
|
|
210
|
+
--foreground: ${text};
|
|
211
|
+
--card: ${surface};
|
|
212
|
+
--card-foreground: ${text};
|
|
213
|
+
--popover: ${surface};
|
|
214
|
+
--popover-foreground: ${text};
|
|
215
|
+
--primary: ${accent};
|
|
216
|
+
--primary-foreground: ${primaryFg};
|
|
217
|
+
--secondary: ${surface};
|
|
218
|
+
--secondary-foreground: ${textMuted};
|
|
219
|
+
--muted: ${surface};
|
|
220
|
+
--muted-foreground: ${textMuted};
|
|
221
|
+
--accent: ${surface};
|
|
222
|
+
--accent-foreground: ${accentFg};
|
|
223
|
+
--destructive: 0 84.2% 60.2%;
|
|
224
|
+
--destructive-foreground: 0 0% 98%;
|
|
225
|
+
--border: ${border};
|
|
226
|
+
--input: ${border};
|
|
227
|
+
--ring: ${accent};
|
|
228
|
+
--radius: ${radiusRem};
|
|
229
|
+
--font-heading: '${seed.headingFont}', sans-serif;
|
|
230
|
+
--font-body: '${seed.bodyFont}', sans-serif;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/*
|
|
235
|
+
Design brief:
|
|
236
|
+
${seed.aiPromptRules}
|
|
237
|
+
*/`;
|
|
238
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SeedFlip MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Gives AI agents access to 100+ curated design systems.
|
|
6
|
+
* When an agent needs a design direction, it calls SeedFlip
|
|
7
|
+
* instead of guessing colors and fonts.
|
|
8
|
+
*
|
|
9
|
+
* Tools:
|
|
10
|
+
* get_design_seed — Get a curated design system by reference, vibe, or style
|
|
11
|
+
* list_design_seeds — Browse available seeds with filtering
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SeedFlip MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Gives AI agents access to 100+ curated design systems.
|
|
6
|
+
* When an agent needs a design direction, it calls SeedFlip
|
|
7
|
+
* instead of guessing colors and fonts.
|
|
8
|
+
*
|
|
9
|
+
* Tools:
|
|
10
|
+
* get_design_seed — Get a curated design system by reference, vibe, or style
|
|
11
|
+
* list_design_seeds — Browse available seeds with filtering
|
|
12
|
+
*/
|
|
13
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
14
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import { readFileSync } from 'fs';
|
|
17
|
+
import { dirname, join } from 'path';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
import { searchSeeds } from './search.js';
|
|
20
|
+
import { formatTokens, formatTailwind, formatCSS, formatShadcn, } from './exporters.js';
|
|
21
|
+
// ── Load seed data ───────────────────────────────────────────────
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const seedsPath = join(__dirname, 'seeds-data.json');
|
|
24
|
+
const seeds = JSON.parse(readFileSync(seedsPath, 'utf-8'));
|
|
25
|
+
// ── Server setup ─────────────────────────────────────────────────
|
|
26
|
+
const server = new McpServer({
|
|
27
|
+
name: 'seedflip',
|
|
28
|
+
version: '0.1.0',
|
|
29
|
+
});
|
|
30
|
+
// ── Tool: get_design_seed ────────────────────────────────────────
|
|
31
|
+
server.tool('get_design_seed', `Get a curated design system for your project. Returns production-ready design tokens (fonts, colors, spacing, shadows, border radius) with implementation guidance.
|
|
32
|
+
|
|
33
|
+
Accepts:
|
|
34
|
+
- Brand references: "Stripe", "Vercel", "Linear", "GitHub", "Notion", "Supabase", "Spotify", "Framer", "Resend", "Superhuman", "Raycast", "Arc", "Railway", "Tailwind"
|
|
35
|
+
- Style descriptors: "dark", "light", "minimal", "brutalist", "warm", "elegant", "editorial", "neon", "cyberpunk", "retro", "professional", "luxury", "developer"
|
|
36
|
+
- Vibes: "dark minimal SaaS", "warm editorial blog", "brutalist portfolio", "neon cyberpunk"
|
|
37
|
+
- Seed names: "Nightfall", "Ivory", "Concrete", etc.
|
|
38
|
+
- Or no query for a random curated seed.
|
|
39
|
+
|
|
40
|
+
Returns complete design tokens in your requested format (tokens, tailwind, css, or shadcn).`, {
|
|
41
|
+
query: z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe('What kind of design direction you want. Examples: "Stripe", "dark minimal", "warm editorial", "brutalist", "neon developer tool"'),
|
|
45
|
+
format: z
|
|
46
|
+
.enum(['tokens', 'tailwind', 'css', 'shadcn'])
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('Output format. "tokens" = readable summary with all values and design brief. "tailwind" = tailwind.config.ts. "css" = CSS custom properties. "shadcn" = shadcn/ui theme globals.css. Defaults to tokens.'),
|
|
49
|
+
count: z
|
|
50
|
+
.number()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('Number of seeds to return (1-5). Defaults to 1. Use more to give options.'),
|
|
53
|
+
}, async ({ query, format = 'tokens', count = 1 }) => {
|
|
54
|
+
const results = searchSeeds(seeds, query ?? '');
|
|
55
|
+
const limit = Math.min(Math.max(count, 1), 5);
|
|
56
|
+
const topSeeds = results.slice(0, limit);
|
|
57
|
+
if (topSeeds.length === 0) {
|
|
58
|
+
// Fallback to random
|
|
59
|
+
const idx = Math.floor(Math.random() * seeds.length);
|
|
60
|
+
topSeeds.push({ seed: seeds[idx], score: 0, matchReasons: ['random (no match found)'] });
|
|
61
|
+
}
|
|
62
|
+
const formatter = format === 'tailwind'
|
|
63
|
+
? formatTailwind
|
|
64
|
+
: format === 'css'
|
|
65
|
+
? formatCSS
|
|
66
|
+
: format === 'shadcn'
|
|
67
|
+
? formatShadcn
|
|
68
|
+
: formatTokens;
|
|
69
|
+
const sections = topSeeds.map((result, i) => {
|
|
70
|
+
const header = limit > 1
|
|
71
|
+
? `### Option ${i + 1}: ${result.seed.name} (score: ${result.score}, matched: ${result.matchReasons.join(', ')})\n\n`
|
|
72
|
+
: '';
|
|
73
|
+
return header + formatter(result.seed);
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
content: [
|
|
77
|
+
{
|
|
78
|
+
type: 'text',
|
|
79
|
+
text: sections.join('\n\n---\n\n'),
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
// ── Tool: list_design_seeds ──────────────────────────────────────
|
|
85
|
+
server.tool('list_design_seeds', `List available SeedFlip design seeds. Use this to see what's available before picking one, or to filter by tag/style. Returns seed names, vibes, and tags.`, {
|
|
86
|
+
tag: z
|
|
87
|
+
.string()
|
|
88
|
+
.optional()
|
|
89
|
+
.describe('Filter by tag. Examples: "dark", "light", "brutalist", "warm", "editorial", "developer", "neon"'),
|
|
90
|
+
}, async ({ tag }) => {
|
|
91
|
+
let filtered = seeds;
|
|
92
|
+
if (tag) {
|
|
93
|
+
const tagLower = tag.toLowerCase();
|
|
94
|
+
filtered = seeds.filter((s) => s.tags.some((t) => t.toLowerCase() === tagLower));
|
|
95
|
+
}
|
|
96
|
+
// Skip wayback collection unless specifically asked for
|
|
97
|
+
if (!tag || !['retro', 'vintage', 'nostalgic', 'wayback'].includes(tag.toLowerCase())) {
|
|
98
|
+
filtered = filtered.filter((s) => s.collection !== 'wayback');
|
|
99
|
+
}
|
|
100
|
+
const isDark = (bg) => {
|
|
101
|
+
const hex = bg.replace('#', '');
|
|
102
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
103
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
104
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
105
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 < 0.5;
|
|
106
|
+
};
|
|
107
|
+
const lines = filtered.map((s) => `- **${s.name}** — ${s.vibe} [${isDark(s.bg) ? 'dark' : 'light'}] (${s.tags.join(', ')})`);
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: 'text',
|
|
112
|
+
text: `## SeedFlip — ${filtered.length} seeds${tag ? ` matching "${tag}"` : ''}\n\n${lines.join('\n')}`,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
// ── Start ────────────────────────────────────────────────────────
|
|
118
|
+
async function main() {
|
|
119
|
+
const transport = new StdioServerTransport();
|
|
120
|
+
await server.connect(transport);
|
|
121
|
+
}
|
|
122
|
+
main().catch(console.error);
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SeedFlip MCP — Search Engine
|
|
3
|
+
*
|
|
4
|
+
* Scores seeds against a natural language query.
|
|
5
|
+
* Handles brand references ("Stripe"), vibes ("dark minimal"),
|
|
6
|
+
* and style descriptors ("brutalist", "warm editorial").
|
|
7
|
+
*/
|
|
8
|
+
interface DesignSeed {
|
|
9
|
+
name: string;
|
|
10
|
+
fakeUrl: string;
|
|
11
|
+
vibe: string;
|
|
12
|
+
tags: string[];
|
|
13
|
+
headingFont: string;
|
|
14
|
+
bodyFont: string;
|
|
15
|
+
headingWeight: number;
|
|
16
|
+
letterSpacing: string;
|
|
17
|
+
bg: string;
|
|
18
|
+
surface: string;
|
|
19
|
+
surfaceHover: string;
|
|
20
|
+
border: string;
|
|
21
|
+
text: string;
|
|
22
|
+
textMuted: string;
|
|
23
|
+
accent: string;
|
|
24
|
+
accentSoft: string;
|
|
25
|
+
radius: string;
|
|
26
|
+
radiusSm: string;
|
|
27
|
+
radiusXl: string;
|
|
28
|
+
shadow: string;
|
|
29
|
+
shadowSm: string;
|
|
30
|
+
shadowStyle: string;
|
|
31
|
+
gradient: string;
|
|
32
|
+
aiPromptTypography: string;
|
|
33
|
+
aiPromptColors: string;
|
|
34
|
+
aiPromptShape: string;
|
|
35
|
+
aiPromptDepth: string;
|
|
36
|
+
aiPromptRules: string;
|
|
37
|
+
collection?: string;
|
|
38
|
+
}
|
|
39
|
+
export type { DesignSeed };
|
|
40
|
+
export interface ScoredSeed {
|
|
41
|
+
seed: DesignSeed;
|
|
42
|
+
score: number;
|
|
43
|
+
matchReasons: string[];
|
|
44
|
+
}
|
|
45
|
+
export declare function searchSeeds(seeds: DesignSeed[], query: string): ScoredSeed[];
|