@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 +21 -0
- package/README.md +63 -0
- package/bin/design-md-mcp.mjs +7 -0
- package/package.json +29 -0
- package/src/server.mjs +242 -0
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
|
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
|
+
}
|