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 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
+ }
@@ -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);
@@ -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[];