dembrandt 0.8.2 → 0.9.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 CHANGED
@@ -32,6 +32,29 @@ Or use npx without installing: `npx dembrandt bmw.de`
32
32
 
33
33
  Requires Node.js 18+
34
34
 
35
+ ## AI Agent Integration (MCP)
36
+
37
+ Use Dembrandt as a tool in Claude Code, Cursor, Windsurf, or any MCP-compatible client. Ask your agent to "extract the color palette from stripe.com" and it calls Dembrandt automatically.
38
+
39
+ ```bash
40
+ claude mcp add --transport stdio dembrandt -- npx -y dembrandt-mcp
41
+ ```
42
+
43
+ Or add to your project's `.mcp.json`:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "dembrandt": {
49
+ "command": "npx",
50
+ "args": ["-y", "dembrandt-mcp"]
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ 7 tools available: `get_design_tokens`, `get_color_palette`, `get_typography`, `get_component_styles`, `get_surfaces`, `get_spacing`, `get_brand_identity`.
57
+
35
58
  ## What to expect from extraction?
36
59
 
37
60
  - Colors (semantic, palette, CSS variables)
package/index.js CHANGED
@@ -23,7 +23,7 @@ import { join } from "path";
23
23
  program
24
24
  .name("dembrandt")
25
25
  .description("Extract design tokens from any website")
26
- .version("0.8.2")
26
+ .version("0.9.0")
27
27
  .argument("<url>")
28
28
  .option("--browser <type>", "Browser to use (chromium|firefox)", "chromium")
29
29
  .option("--json-only", "Output raw JSON")
package/mcp-server.js ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Dembrandt MCP Server
5
+ *
6
+ * Extract design tokens from any live website. Works with Claude Code, Cursor,
7
+ * Windsurf, and any MCP-compatible client.
8
+ *
9
+ * Install:
10
+ * claude mcp add --transport stdio dembrandt -- npx -y dembrandt-mcp
11
+ */
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 { chromium } from "playwright-core";
17
+ import { readFileSync } from "node:fs";
18
+ import { extractBranding } from "./lib/extractors.js";
19
+
20
+ const { version } = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8"));
21
+
22
+ const server = new McpServer({
23
+ name: "dembrandt",
24
+ version,
25
+ });
26
+
27
+ // extractBranding expects a spinner — stub it for MCP context
28
+ const nullSpinner = {
29
+ text: "",
30
+ start(msg) { this.text = msg; return this; },
31
+ stop() { return this; },
32
+ succeed(msg) { return this; },
33
+ fail(msg) { return this; },
34
+ };
35
+
36
+ /**
37
+ * Run extraction with error handling suitable for MCP responses.
38
+ * Returns { ok, data?, error? } so tool handlers never throw.
39
+ */
40
+ async function runExtraction(url, options = {}) {
41
+ if (!/^https?:\/\//i.test(url)) url = "https://" + url;
42
+ let browser;
43
+ try {
44
+ browser = await chromium.launch({
45
+ headless: true,
46
+ args: ["--disable-blink-features=AutomationControlled"],
47
+ });
48
+ } catch (err) {
49
+ return {
50
+ ok: false,
51
+ error: `Browser launch failed. Is Playwright installed? Run: npx playwright install chromium\n\n${err.message}`,
52
+ };
53
+ }
54
+
55
+ // Suppress console output — extractors.js writes directly to stdout
56
+ // which would corrupt the JSON-RPC stream
57
+ const _log = console.log;
58
+ const _warn = console.warn;
59
+ const _error = console.error;
60
+ console.log = () => {};
61
+ console.warn = () => {};
62
+ console.error = () => {};
63
+
64
+ try {
65
+ const data = await extractBranding(url, nullSpinner, browser, {
66
+ navigationTimeout: 90000,
67
+ slow: options.slow || false,
68
+ darkMode: options.darkMode || false,
69
+ mobile: options.mobile || false,
70
+ });
71
+ return { ok: true, data };
72
+ } catch (err) {
73
+ const msg = err.message || String(err);
74
+ if (msg.includes("timeout") || msg.includes("Timeout")) {
75
+ return { ok: false, error: `Extraction timed out for ${url}. Try with slow: true for heavy SPAs.` };
76
+ }
77
+ if (msg.includes("net::ERR_NAME_NOT_RESOLVED")) {
78
+ return { ok: false, error: `Could not resolve ${url}. Check the URL.` };
79
+ }
80
+ if (msg.includes("net::ERR_CONNECTION_REFUSED")) {
81
+ return { ok: false, error: `Connection refused by ${url}.` };
82
+ }
83
+ return { ok: false, error: `Extraction failed for ${url}: ${msg}` };
84
+ } finally {
85
+ console.log = _log;
86
+ console.warn = _warn;
87
+ console.error = _error;
88
+ await browser.close().catch(() => {});
89
+ }
90
+ }
91
+
92
+ function jsonResult(data) {
93
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
94
+ }
95
+
96
+ function errorResult(message) {
97
+ return { content: [{ type: "text", text: message }], isError: true };
98
+ }
99
+
100
+ /**
101
+ * Wrapper that handles extraction + error formatting for all tools.
102
+ * `pick` receives the full result and returns the filtered subset.
103
+ */
104
+ function toolHandler(pick, extraOptions = {}) {
105
+ return async (params) => {
106
+ const { url, slow, darkMode } = params;
107
+ const result = await runExtraction(url, { slow, darkMode, ...extraOptions });
108
+ if (!result.ok) return errorResult(result.error);
109
+ return jsonResult(pick(result.data));
110
+ };
111
+ }
112
+
113
+ // ── Shared params ──────────────────────────────────────────────────────
114
+
115
+ const url = z.string().describe("Website URL (e.g. stripe.com)");
116
+ const slow = z.boolean().optional().default(false).describe("3x timeouts for heavy SPAs");
117
+
118
+ // ── Tools ──────────────────────────────────────────────────────────────
119
+
120
+ server.tool(
121
+ "get_design_tokens",
122
+ "Extract the full design system from a live website. Launches a real browser, navigates to the site, and returns production-ready design tokens: color palette (hex, RGB, LCH, OKLCH) with semantic roles and CSS custom properties, typography scale (families, fallbacks, sizes, weights, line heights, letter spacing by context), spacing system with grid detection, border radii, border patterns, box shadows for elevation, component styles (buttons with hover/focus states, inputs, links, badges), responsive breakpoints, logo and favicons, site name, detected CSS frameworks, and icon systems. Takes 15-40 seconds depending on site complexity.",
123
+ { url, slow },
124
+ toolHandler((d) => d),
125
+ );
126
+
127
+ server.tool(
128
+ "get_color_palette",
129
+ "Extract brand colors from a live website. Returns semantic colors (primary, secondary, accent), full palette ranked by usage frequency and confidence (high/medium/low), CSS custom properties with their design-system names, and hover/focus state colors discovered by simulating real user interactions. Each color in hex, RGB, LCH, and OKLCH.",
130
+ {
131
+ url, slow,
132
+ darkMode: z.boolean().optional().default(false).describe("Also extract dark mode palette"),
133
+ },
134
+ toolHandler((d) => ({ url: d.url, colors: d.colors })),
135
+ );
136
+
137
+ server.tool(
138
+ "get_typography",
139
+ "Extract typography from a live website. Returns every font family with its fallback stack, the complete type scale grouped by context (heading, body, button, link, caption) with pixel and rem sizes, weights, line heights, letter spacing, and text transforms. Also reports font sources: Google Fonts URLs, Adobe Fonts usage, and variable font detection.",
140
+ { url, slow },
141
+ toolHandler((d) => ({ url: d.url, typography: d.typography })),
142
+ );
143
+
144
+ server.tool(
145
+ "get_component_styles",
146
+ "Extract UI component styles from a live website. Returns button variants with default, hover, active, and focus states (background, text color, padding, border radius, border, shadow, outline, opacity), input field styles (border, focus ring, padding, placeholder), link styles (color, text decoration, hover changes), and badge/tag styles.",
147
+ { url, slow },
148
+ toolHandler((d) => ({ url: d.url, components: d.components })),
149
+ );
150
+
151
+ server.tool(
152
+ "get_surfaces",
153
+ "Extract surface treatment tokens from a live website: border radii with element context (which radii are used on buttons vs cards vs inputs vs modals), border patterns (width + style + color combinations), and box shadow elevation levels.",
154
+ { url, slow },
155
+ toolHandler((d) => ({
156
+ url: d.url,
157
+ borderRadius: d.borderRadius,
158
+ borders: d.borders,
159
+ shadows: d.shadows,
160
+ })),
161
+ );
162
+
163
+ server.tool(
164
+ "get_spacing",
165
+ "Extract the spacing system from a live website: common margin and padding values sorted by frequency, pixel and rem values, and grid system detection (4px, 8px, or custom scale).",
166
+ { url, slow },
167
+ toolHandler((d) => ({ url: d.url, spacing: d.spacing })),
168
+ );
169
+
170
+ server.tool(
171
+ "get_brand_identity",
172
+ "Extract brand identity from a live website: site name, logo (source, dimensions, safe zone), all favicon variants (icon, apple-touch-icon, og:image, twitter:image with sizes and URLs), detected CSS frameworks (Tailwind, Bootstrap, MUI, etc.), icon systems (Font Awesome, Material Icons, SVG), and responsive breakpoints.",
173
+ { url, slow },
174
+ toolHandler((d) => ({
175
+ url: d.url,
176
+ siteName: d.siteName,
177
+ logo: d.logo,
178
+ favicons: d.favicons,
179
+ frameworks: d.frameworks,
180
+ iconSystem: d.iconSystem,
181
+ breakpoints: d.breakpoints,
182
+ })),
183
+ );
184
+
185
+ // ── Start ──────────────────────────────────────────────────────────────
186
+
187
+ const transport = new StdioServerTransport();
188
+ await server.connect(transport);
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "dembrandt",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "Extract design tokens and brand assets from any website",
5
+ "mcpName": "io.github.dembrandt/dembrandt",
5
6
  "main": "index.js",
6
7
  "type": "module",
7
8
  "bin": {
8
- "dembrandt": "index.js"
9
+ "dembrandt": "index.js",
10
+ "dembrandt-mcp": "mcp-server.js"
9
11
  },
10
12
  "files": [
11
13
  "index.js",
14
+ "mcp-server.js",
12
15
  "lib/"
13
16
  ],
14
17
  "scripts": {
@@ -41,12 +44,14 @@
41
44
  "author": "thevangelist <info@esajuhana.com>",
42
45
  "license": "MIT",
43
46
  "dependencies": {
47
+ "@modelcontextprotocol/sdk": "1.29.0",
44
48
  "@playwright/browser-chromium": "^1.57.0",
45
49
  "@playwright/browser-firefox": "^1.57.0",
46
50
  "chalk": "^5.3.0",
47
51
  "commander": "^11.1.0",
48
52
  "ora": "^7.0.1",
49
- "playwright-core": "^1.57.0"
53
+ "playwright-core": "^1.57.0",
54
+ "zod": "4.3.6"
50
55
  },
51
56
  "engines": {
52
57
  "node": ">=18.0.0"