design_extract_tokens
Extract design tokens like colors, typography, and spacing from any live website URL to analyze and implement visual styles.
Instructions
Extract actual design tokens (colors, typography, spacing, borders, shadows) from a live website using headless browser. Give it any URL and get back the exact values used. Pairs well with the search tools — find inspiration, then extract tokens from sites you like.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Website URL to extract design tokens from. Examples: "https://stripe.com", "https://linear.app" | |
| dark_mode | No | Extract colors from dark mode variant | |
| mobile | No | Extract from mobile viewport (375px) |
Implementation Reference
- src/index.ts:525-550 (handler)The tool handler function for "design_extract_tokens" which orchestrates the call to `runDembrandt` and `formatTokens`.
}, async (params: ExtractTokensInput) => { try { const url = params.url.startsWith("http") ? params.url : `https://${params.url}`; const flags: string[] = []; if (params.dark_mode) flags.push("--dark-mode"); if (params.mobile) flags.push("--mobile"); const stdout = await runDembrandt(url, flags); const tokens: DesignTokens = JSON.parse(stdout); const text = formatTokens(tokens, url); return { content: [{ type: "text" as const, text }], structuredContent: { url, dark_mode: params.dark_mode, mobile: params.mobile, tokens }, }; } catch (error) { return { content: [ { type: "text" as const, text: error instanceof Error ? error.message : `Error: ${String(error)}`, }, ], }; } }); - src/index.ts:496-511 (schema)Zod schema defining the inputs for the design_extract_tokens tool.
const ExtractTokensInputSchema = z .object({ url: z .string() .min(4, "URL is required") .describe('Website URL to extract design tokens from. Examples: "https://stripe.com", "https://linear.app"'), dark_mode: z .boolean() .default(false) .describe("Extract colors from dark mode variant"), mobile: z .boolean() .default(false) .describe("Extract from mobile viewport (375px)"), }) .strict(); - src/index.ts:515-524 (registration)Tool registration for "design_extract_tokens" with the MCP server.
server.registerTool("design_extract_tokens", { title: "Extract design tokens from website", description: `Extract actual design tokens (colors, typography, spacing, borders, shadows) from a live website using headless browser. Give it any URL and get back the exact values used. Pairs well with the search tools — find inspiration, then extract tokens from sites you like.`, inputSchema: ExtractTokensInputSchema, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, - src/index.ts:407-420 (helper)Helper function to execute the dembrandt command-line tool.
function runDembrandt(url: string, flags: string[]): Promise<string> { return new Promise((resolve, reject) => { const args = [url, "--json-only", ...flags]; execFile("dembrandt", args, { timeout: 60_000, maxBuffer: 5 * 1024 * 1024 }, (error, stdout, stderr) => { if (error) { const msg = stderr?.trim() || error.message; if (error.killed) return reject(new Error("Timed out after 60s. The site may be too slow — try with --slow via CLI.")); if (msg.includes("net::ERR_NAME_NOT_RESOLVED")) return reject(new Error(`Could not resolve URL: ${url}`)); if (msg.includes("net::ERR_CONNECTION_REFUSED")) return reject(new Error(`Connection refused: ${url}`)); return reject(new Error(`dembrandt failed: ${msg}`)); } resolve(stdout); }); }); - src/index.ts:423-494 (helper)Helper function to format the design tokens into a Markdown representation.
function formatTokens(tokens: DesignTokens, url: string): string { const lines = [`# Design Tokens: ${url}`, ""]; if (tokens.colors && Object.keys(tokens.colors).length) { lines.push("## Colors", ""); for (const [name, value] of Object.entries(tokens.colors)) { if (typeof value === "string") { lines.push(`- **${name}**: \`${value}\``); } else if (typeof value === "object" && value !== null) { lines.push(`- **${name}**: \`${JSON.stringify(value)}\``); } } lines.push(""); } if (tokens.typography && Object.keys(tokens.typography).length) { lines.push("## Typography", ""); for (const [name, value] of Object.entries(tokens.typography)) { if (typeof value === "string") { lines.push(`- **${name}**: \`${value}\``); } else if (typeof value === "object" && value !== null) { lines.push(`- **${name}**: \`${JSON.stringify(value)}\``); } } lines.push(""); } if (tokens.spacing && Object.keys(tokens.spacing).length) { lines.push("## Spacing", ""); for (const [name, value] of Object.entries(tokens.spacing)) { lines.push(`- **${name}**: \`${JSON.stringify(value)}\``); } lines.push(""); } if (tokens.borders && Object.keys(tokens.borders).length) { lines.push("## Borders", ""); for (const [name, value] of Object.entries(tokens.borders)) { lines.push(`- **${name}**: \`${JSON.stringify(value)}\``); } lines.push(""); } if (tokens.shadows && Object.keys(tokens.shadows).length) { lines.push("## Shadows", ""); for (const [name, value] of Object.entries(tokens.shadows)) { lines.push(`- **${name}**: \`${JSON.stringify(value)}\``); } lines.push(""); } // Any remaining top-level keys const handled = new Set(["colors", "typography", "spacing", "borders", "shadows"]); for (const [key, value] of Object.entries(tokens)) { if (handled.has(key) || value === undefined || value === null) continue; lines.push(`## ${key.charAt(0).toUpperCase() + key.slice(1)}`, ""); if (typeof value === "object") { for (const [k, v] of Object.entries(value as Record<string, unknown>)) { lines.push(`- **${k}**: \`${typeof v === "string" ? v : JSON.stringify(v)}\``); } } else { lines.push(`- ${JSON.stringify(value)}`); } lines.push(""); } let result = lines.join("\n"); if (result.length > CHARACTER_LIMIT) { result = result.slice(0, CHARACTER_LIMIT) + "\n\n...(truncated)"; } return result; }