@webdesignhot/design-md-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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 webdesignhot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @webdesignhot/design-md-mcp
2
+
3
+ > MCP server exposing the [webdesignhot.com](https://www.webdesignhot.com/design.md/) DESIGN.md catalog (254+ real-brand design systems) to Claude Desktop, Claude Code, Cursor, Cline, and any other MCP-aware AI agent.
4
+
5
+ ## Why
6
+
7
+ When an AI coding agent picks up a project, it usually has no idea what your visual style is. You can paste tokens into the system prompt, hand it a Figma export, or — best — point it at a single `DESIGN.md` file. This MCP server takes that one step further: agents can browse 254+ real-brand design systems (Linear, Stripe, Vercel, Anthropic, Notion, Apple, Tesla, BMW, …) and install one in your project without you ever leaving the chat.
8
+
9
+ ## Tools
10
+
11
+ - **`list_designs`** — list every design (with `featured_only` / `category` / `tag` filters)
12
+ - **`get_design`** — full DESIGN.md/v1.5 source for a slug
13
+ - **`search_designs`** — fuzzy search by name, tagline, tags, categories
14
+ - **`diff_designs`** — token-level diff between any two designs
15
+ - **`export_design`** — render tokens to tailwind / css / dtcg / figma
16
+ - **`install_design`** — get the npx command + raw markdown to install one
17
+
18
+ ## Install
19
+
20
+ ### Claude Desktop / Claude Code
21
+
22
+ Add to your `~/Library/Application Support/Claude/claude_desktop_config.json` (or the equivalent on Windows / Linux):
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "design-md": {
28
+ "command": "npx",
29
+ "args": ["-y", "@webdesignhot/design-md-mcp"]
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ ### Cursor
36
+
37
+ In Settings → Features → MCP → "Add new MCP server":
38
+
39
+ - **Name**: `design-md`
40
+ - **Type**: `command`
41
+ - **Command**: `npx -y @webdesignhot/design-md-mcp`
42
+
43
+ ### Cline / Roo / etc.
44
+
45
+ Any MCP client supporting stdio transports works. After install the binary is `design-md-mcp` (no scope) — run it directly, or invoke via `npx -y @webdesignhot/design-md-mcp`.
46
+
47
+ ## Use it
48
+
49
+ Once connected, ask your agent things like:
50
+
51
+ > *"List the top 10 dark editorial design systems."*
52
+ >
53
+ > *"Install Stripe's DESIGN.md into this project."*
54
+ >
55
+ > *"Diff Linear and Vercel — what changes if I switch?"*
56
+ >
57
+ > *"Export Anthropic's tokens as Tailwind config."*
58
+
59
+ The agent calls the right tool, the response flows back, and you stay in the IDE.
60
+
61
+ ## License
62
+
63
+ MIT — © 2026 webdesignhot
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { startServer } from '../src/server.mjs'
3
+
4
+ startServer().catch((err) => {
5
+ process.stderr.write('design-md-mcp fatal: ' + (err?.message ?? err) + '\n')
6
+ process.exit(1)
7
+ })
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@webdesignhot/design-md-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server exposing the webdesignhot.com DESIGN.md catalog (254+ real-brand design systems) to Claude Desktop, Claude Code, Cursor, Cline, and any other MCP-aware AI agent. Tools: list, get, search, diff, export.",
5
+ "keywords": ["mcp", "model-context-protocol", "design.md", "design-system", "ai-agents", "claude", "cursor"],
6
+ "author": "webdesignhot",
7
+ "license": "MIT",
8
+ "homepage": "https://www.webdesignhot.com/design.md/",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/WebDesignHot/design-md"
12
+ },
13
+ "bin": {
14
+ "design-md-mcp": "bin/design-md-mcp.mjs"
15
+ },
16
+ "type": "module",
17
+ "files": [
18
+ "bin",
19
+ "src",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "dependencies": {
27
+ "@modelcontextprotocol/sdk": "^1.0.0"
28
+ }
29
+ }
package/src/server.mjs ADDED
@@ -0,0 +1,242 @@
1
+ /**
2
+ * design-md MCP server — exposes the webdesignhot.com DESIGN.md catalog
3
+ * as MCP tools so any MCP-aware agent (Claude Desktop, Claude Code,
4
+ * Cursor, Cline, Roo, etc.) can list/search/get/diff/export design
5
+ * systems without leaving the IDE.
6
+ *
7
+ * Stdio transport. Talks to the same prerendered endpoints the CLI uses.
8
+ */
9
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js'
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
11
+ import {
12
+ CallToolRequestSchema,
13
+ ListToolsRequestSchema,
14
+ } from '@modelcontextprotocol/sdk/types.js'
15
+
16
+ const BASE = process.env.DESIGN_MD_BASE || 'https://www.webdesignhot.com'
17
+
18
+ let _catalog = null
19
+ async function fetchCatalog() {
20
+ if (_catalog) return _catalog
21
+ const res = await fetch(`${BASE}/api/design-md/index.json`)
22
+ if (!res.ok) throw new Error(`Catalog fetch failed: ${res.status}`)
23
+ _catalog = await res.json()
24
+ return _catalog
25
+ }
26
+ async function fetchRaw(slug) {
27
+ const res = await fetch(`${BASE}/api/design-md/${slug}.md`)
28
+ if (res.status === 404) throw new Error(`No design "${slug}". Use list_designs.`)
29
+ if (!res.ok) throw new Error(`Raw fetch failed: ${res.status}`)
30
+ return res.text()
31
+ }
32
+ async function fetchExport(slug, format) {
33
+ const res = await fetch(`${BASE}/api/design-md/${slug}/export/${format}`)
34
+ if (!res.ok) throw new Error(`Export ${format} failed: ${res.status}`)
35
+ return res.text()
36
+ }
37
+
38
+ const TOOLS = [
39
+ {
40
+ name: 'list_designs',
41
+ description:
42
+ 'List every design in the webdesignhot.com catalog. Returns slug, name, tagline, categories, tags, and a 3-color preview swatch per entry. Use this to discover what is available before calling get_design.',
43
+ inputSchema: {
44
+ type: 'object',
45
+ properties: {
46
+ featured_only: { type: 'boolean', description: 'Filter to featured (curated) designs only.' },
47
+ category: { type: 'string', description: 'Filter by single category (e.g. "dev-tools", "fintech", "ai", "media").' },
48
+ tag: { type: 'string', description: 'Filter by single tag (e.g. "dark", "editorial", "minimal").' },
49
+ },
50
+ },
51
+ },
52
+ {
53
+ name: 'get_design',
54
+ description:
55
+ 'Fetch the full DESIGN.md/v1.5 source for a specific design (YAML frontmatter + markdown body). The response is the exact file an agent should read as its style source of truth. Use the agent-prompt header from the file to anchor every component decision.',
56
+ inputSchema: {
57
+ type: 'object',
58
+ required: ['slug'],
59
+ properties: {
60
+ slug: { type: 'string', description: 'The design slug, e.g. "linear", "stripe", "vercel". See list_designs.' },
61
+ },
62
+ },
63
+ },
64
+ {
65
+ name: 'search_designs',
66
+ description:
67
+ 'Fuzzy-search the catalog by name, tagline, tags, or categories. Returns up to 20 matches sorted by relevance. Use when the user says "something like Linear" or "find me dark editorial designs".',
68
+ inputSchema: {
69
+ type: 'object',
70
+ required: ['query'],
71
+ properties: {
72
+ query: { type: 'string', description: 'Free-text query.' },
73
+ limit: { type: 'number', description: 'Max results, default 20.' },
74
+ },
75
+ },
76
+ },
77
+ {
78
+ name: 'diff_designs',
79
+ description:
80
+ 'Token-level diff between two designs. Returns added / removed / modified colors and radii. Useful for "what would change if we migrated from Linear to Stripe?" questions.',
81
+ inputSchema: {
82
+ type: 'object',
83
+ required: ['from', 'to'],
84
+ properties: {
85
+ from: { type: 'string', description: 'From slug.' },
86
+ to: { type: 'string', description: 'To slug.' },
87
+ },
88
+ },
89
+ },
90
+ {
91
+ name: 'export_design',
92
+ description:
93
+ 'Convert a design\'s tokens to a target format. Returns ready-to-paste output for tailwind, css, dtcg, or figma.',
94
+ inputSchema: {
95
+ type: 'object',
96
+ required: ['slug', 'format'],
97
+ properties: {
98
+ slug: { type: 'string' },
99
+ format: { type: 'string', enum: ['tailwind', 'css', 'dtcg', 'figma'] },
100
+ },
101
+ },
102
+ },
103
+ {
104
+ name: 'install_design',
105
+ description:
106
+ 'Get the npx command and the raw .md content to install a design as DESIGN.md in the user\'s project. Returns both the recommended CLI invocation and the full markdown for the agent to write directly if it has filesystem access.',
107
+ inputSchema: {
108
+ type: 'object',
109
+ required: ['slug'],
110
+ properties: {
111
+ slug: { type: 'string' },
112
+ out: { type: 'string', description: 'Output path, default DESIGN.md.' },
113
+ },
114
+ },
115
+ },
116
+ ]
117
+
118
+ function tool(json) {
119
+ return { content: [{ type: 'text', text: typeof json === 'string' ? json : JSON.stringify(json, null, 2) }] }
120
+ }
121
+
122
+ function fuzzy(query, hay) {
123
+ const n = query.toLowerCase()
124
+ const h = String(hay).toLowerCase()
125
+ let i = 0
126
+ for (const c of n) {
127
+ const f = h.indexOf(c, i)
128
+ if (f === -1) return false
129
+ i = f + 1
130
+ }
131
+ return true
132
+ }
133
+
134
+ function parseFrontmatter(md) {
135
+ const m = md.match(/^---\n([\s\S]*?)\n---/)
136
+ if (!m) return {}
137
+ const out = {}
138
+ // Tiny parser — only handles flat color: hex pairs (good enough for diff)
139
+ for (const line of m[1].split('\n')) {
140
+ const km = line.match(/^\s+([a-zA-Z][\w-]*):\s*['"]?(#[0-9a-fA-F]{3,8}|rgba?\([^)]+\))['"]?/)
141
+ if (km) out[km[1]] = km[2]
142
+ }
143
+ return out
144
+ }
145
+
146
+ function diffMaps(a, b) {
147
+ const result = { added: {}, removed: {}, modified: {} }
148
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)])
149
+ for (const k of keys) {
150
+ if (a[k] == null && b[k] != null) result.added[k] = b[k]
151
+ else if (a[k] != null && b[k] == null) result.removed[k] = a[k]
152
+ else if (a[k] !== b[k]) result.modified[k] = { from: a[k], to: b[k] }
153
+ }
154
+ return result
155
+ }
156
+
157
+ const HANDLERS = {
158
+ async list_designs({ featured_only, category, tag }) {
159
+ const c = await fetchCatalog()
160
+ let entries = c.entries
161
+ if (featured_only) entries = entries.filter((e) => e.featured)
162
+ if (category) entries = entries.filter((e) => (e.categories ?? []).includes(category))
163
+ if (tag) entries = entries.filter((e) => (e.tags ?? []).includes(tag))
164
+ return tool({
165
+ count: entries.length,
166
+ entries: entries.map((e) => ({
167
+ slug: e.slug,
168
+ name: e.name,
169
+ tagline: e.tagline,
170
+ categories: e.categories,
171
+ tags: e.tags,
172
+ featured: e.featured,
173
+ preview_swatch: e.preview_swatch,
174
+ })),
175
+ })
176
+ },
177
+
178
+ async get_design({ slug }) {
179
+ const md = await fetchRaw(slug)
180
+ return tool(md)
181
+ },
182
+
183
+ async search_designs({ query, limit }) {
184
+ const c = await fetchCatalog()
185
+ const lim = Math.min(limit ?? 20, 50)
186
+ const matches = c.entries
187
+ .filter((e) => {
188
+ const hay = `${e.name} ${e.tagline} ${(e.tags ?? []).join(' ')} ${(e.categories ?? []).join(' ')}`
189
+ return fuzzy(query, hay)
190
+ })
191
+ .slice(0, lim)
192
+ return tool({ query, count: matches.length, matches: matches.map((e) => ({ slug: e.slug, name: e.name, tagline: e.tagline, tags: e.tags })) })
193
+ },
194
+
195
+ async diff_designs({ from, to }) {
196
+ const [a, b] = await Promise.all([fetchRaw(from), fetchRaw(to)])
197
+ const fa = parseFrontmatter(a)
198
+ const fb = parseFrontmatter(b)
199
+ return tool({ from, to, colors: diffMaps(fa, fb) })
200
+ },
201
+
202
+ async export_design({ slug, format }) {
203
+ const text = await fetchExport(slug, format)
204
+ return tool(text)
205
+ },
206
+
207
+ async install_design({ slug, out }) {
208
+ const md = await fetchRaw(slug)
209
+ return tool({
210
+ command: `npx design-md add ${slug}${out ? ` --out ${out}` : ''}`,
211
+ out: out || 'DESIGN.md',
212
+ bytes: md.length,
213
+ markdown: md,
214
+ hint:
215
+ 'If you have filesystem access, write `markdown` to `out`. Otherwise instruct the user to run `command`. Then read DESIGN.md as the visual source of truth — every color, type scale, radius, motion, accessibility token must come from it.',
216
+ })
217
+ },
218
+ }
219
+
220
+ export async function startServer() {
221
+ const server = new Server(
222
+ { name: 'design-md', version: '0.1.0' },
223
+ { capabilities: { tools: {} } },
224
+ )
225
+
226
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }))
227
+
228
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
229
+ const { name, arguments: args } = req.params
230
+ const handler = HANDLERS[name]
231
+ if (!handler) throw new Error(`Unknown tool: ${name}`)
232
+ try {
233
+ return await handler(args ?? {})
234
+ } catch (err) {
235
+ return tool({ error: (err && err.message) || String(err) })
236
+ }
237
+ })
238
+
239
+ const transport = new StdioServerTransport()
240
+ await server.connect(transport)
241
+ // Stays alive on stdio
242
+ }