From 28b94b211d1d50561a03b7fa66aeb839b2293472 Mon Sep 17 00:00:00 2001 From: Dallas Peters Date: Thu, 5 Feb 2026 10:42:02 -0600 Subject: [PATCH 01/28] Add new commands for Optics design system and update package.json - Introduced commands for checking color contrast, creating components, finding tokens, configuring icons, migrating CSS, and conducting design reviews. - Added recipes for sidebar and layout customizations with detailed usage examples. - Updated package.json to remove unnecessary test commands and include new documentation paths. --- .claude/commands/check-contrast.md | 18 + .claude/commands/create-component.md | 18 + .claude/commands/find-token.md | 16 + .claude/commands/icons.md | 29 ++ .claude/commands/migrate-css.md | 29 ++ .claude/commands/recipe.md | 52 +++ .claude/commands/review.md | 17 + .claude/commands/validate-optics.md | 12 + editor-configs/cursor-mcp.json | 9 + package.json | 11 +- src/index.ts | 5 +- src/prompts/configure-icons.ts | 74 ++++ src/prompts/use-recipe.ts | 47 ++ .../prompts/configure-icons-prompt.md | 47 ++ src/resources/prompts/use-recipe-prompt.md | 88 ++++ src/tools/icons.ts | 292 +++++++++++++ src/tools/recipes.ts | 408 ++++++++++++++++++ test-prompts/scaffold-test.md | 139 ++++++ 18 files changed, 1303 insertions(+), 8 deletions(-) create mode 100644 .claude/commands/check-contrast.md create mode 100644 .claude/commands/create-component.md create mode 100644 .claude/commands/find-token.md create mode 100644 .claude/commands/icons.md create mode 100644 .claude/commands/migrate-css.md create mode 100644 .claude/commands/recipe.md create mode 100644 .claude/commands/review.md create mode 100644 .claude/commands/validate-optics.md create mode 100644 editor-configs/cursor-mcp.json create mode 100644 src/prompts/configure-icons.ts create mode 100644 src/prompts/use-recipe.ts create mode 100644 src/resources/prompts/configure-icons-prompt.md create mode 100644 src/resources/prompts/use-recipe-prompt.md create mode 100644 src/tools/icons.ts create mode 100644 src/tools/recipes.ts create mode 100644 test-prompts/scaffold-test.md diff --git a/.claude/commands/check-contrast.md b/.claude/commands/check-contrast.md new file mode 100644 index 0000000..0500de9 --- /dev/null +++ b/.claude/commands/check-contrast.md @@ -0,0 +1,18 @@ +# Check Color Contrast + +Verify WCAG contrast ratio between two Optics color tokens. + +## Instructions + +1. Use the `check_contrast` tool from the optics-mcp server +2. Parse $ARGUMENTS for foreground and background token names +3. Report the contrast ratio and WCAG compliance (AA/AAA) + +## Usage + +``` +/check-contrast primary-on-base primary-base +/check-contrast neutral-on-plus-eight neutral-plus-eight +``` + +If tokens aren't found, suggest similar token names using `search_tokens`. diff --git a/.claude/commands/create-component.md b/.claude/commands/create-component.md new file mode 100644 index 0000000..8f70c97 --- /dev/null +++ b/.claude/commands/create-component.md @@ -0,0 +1,18 @@ +# Create Optics Component + +Generate a component styled with Optics design tokens. + +## Instructions + +Invoke the `create-themed-component` prompt from the optics-mcp server. + +**Arguments from $ARGUMENTS:** +- Component type (e.g., button, card, form, alert) +- Optional: variant (primary, secondary, danger) +- Optional: framework (react, vue, svelte, html) + +## Example + +``` +/create-component button primary react +``` diff --git a/.claude/commands/find-token.md b/.claude/commands/find-token.md new file mode 100644 index 0000000..f86da7c --- /dev/null +++ b/.claude/commands/find-token.md @@ -0,0 +1,16 @@ +# Find Optics Token + +Search for an Optics design token by name or category. + +## Instructions + +1. Use the `search_tokens` tool from the optics-mcp server +2. If $ARGUMENTS contains a category (color, spacing, typography, border, shadow), filter by category +3. Otherwise, search by name pattern +4. Return the matching tokens with their values and descriptions + +## Examples + +- `/find-token primary` → finds all tokens with "primary" in the name +- `/find-token spacing` → lists all spacing tokens +- `/find-token plus-five` → finds scale tokens at +5 level diff --git a/.claude/commands/icons.md b/.claude/commands/icons.md new file mode 100644 index 0000000..8961b98 --- /dev/null +++ b/.claude/commands/icons.md @@ -0,0 +1,29 @@ +# Configure Optics Icons + +Select and configure an icon library for your project. + +## Instructions + +Invoke the `configure-icons` prompt from the optics-mcp server. + +**Arguments from $ARGUMENTS:** +- Library name (material, phosphor, tabler, feather, lucide) +- Or requirements: "need fill", "need weight", "small bundle" + +## Available Libraries + +| Library | Fill | Weight | Emphasis | Size | Notes | +|---------|------|--------|----------|------|-------| +| Material Symbols | ✓ | ✓ | ✓ | ✓ | Default, full features | +| Phosphor | ✓ | ✓ | ✗ | ✓ | Has duotone option | +| Tabler | ✓ | ✗ | ✗ | ✓ | Clean, simple | +| Feather | ✗ | ✗ | ✗ | ✓ | Minimal, small | +| Lucide | ✗ | ✗ | ✗ | ✓ | Feather fork, more icons | + +## Examples + +``` +/icons phosphor +/icons need fill and duotone +/icons smallest bundle +``` diff --git a/.claude/commands/migrate-css.md b/.claude/commands/migrate-css.md new file mode 100644 index 0000000..0929050 --- /dev/null +++ b/.claude/commands/migrate-css.md @@ -0,0 +1,29 @@ +# Migrate to Optics Tokens + +Convert hard-coded CSS values to Optics design tokens. + +## Instructions + +Invoke the `migrate-to-tokens` prompt from the optics-mcp server. + +Pass the CSS code from $ARGUMENTS or current selection. + +## Example + +Input: +```css +.button { + background: #2563eb; + padding: 16px 24px; + border-radius: 4px; +} +``` + +Output: +```css +.button { + background: var(--op-color-primary-base); + padding: var(--op-space-medium) var(--op-space-x-large); + border-radius: var(--op-radius-medium); +} +``` diff --git a/.claude/commands/recipe.md b/.claude/commands/recipe.md new file mode 100644 index 0000000..8ea8bd5 --- /dev/null +++ b/.claude/commands/recipe.md @@ -0,0 +1,52 @@ +# Optics Recipe Command + +Use this command to get real-world customization patterns from Optics. + +## Usage + +Find a recipe that matches your customization needs using the `use-recipe` prompt. + +### Arguments + +- `slug` - Get a specific recipe (e.g., "sidebar-domains", "layout-app-shell") +- `category` - Filter by category: layout, sidebar, header, component +- `query` - Search by keyword + +### Examples + +Get sidebar customization recipe: +``` +/recipe slug:sidebar-domains +``` + +Browse all sidebar recipes: +``` +/recipe category:sidebar +``` + +Search for dark theme examples: +``` +/recipe query:dark +``` + +## Available Recipes + +### Sidebar +- `sidebar-domains` - Domain registrar with wide drawer and custom footer +- `sidebar-16six` - Dark purple performance management sidebar + +### Layout +- `layout-app-shell` - Standard app shell with sidebar, header, main area + +### Header +- `aligned-header` - Three-section header with CSS Grid alignment + +## What's Included + +Each recipe provides: + +1. **Description** - Use case and context +2. **Tokens Used** - Required Optics design tokens +3. **CSS** - Complete scoped stylesheet +4. **HTML** - Working markup example +5. **Docs Link** - Reference to full documentation diff --git a/.claude/commands/review.md b/.claude/commands/review.md new file mode 100644 index 0000000..4f2f10d --- /dev/null +++ b/.claude/commands/review.md @@ -0,0 +1,17 @@ +# Design Review + +Review code for Optics design system compliance. + +## Instructions + +Invoke the `design-review` prompt from the optics-mcp server. + +Pass the code from $ARGUMENTS or current selection for review. + +## What it checks + +- Hard-coded values that should use tokens +- Proper token usage and naming +- Accessibility (color contrast, focus states) +- Consistency with Optics patterns +- Missing or incorrect tokens diff --git a/.claude/commands/validate-optics.md b/.claude/commands/validate-optics.md new file mode 100644 index 0000000..2d51e75 --- /dev/null +++ b/.claude/commands/validate-optics.md @@ -0,0 +1,12 @@ +# Validate Optics Token Usage + +Scan the current file or selection for hard-coded values that should use Optics tokens. + +## Instructions + +1. Use the `validate_token_usage` tool from the optics-mcp server +2. Pass the code from $ARGUMENTS or the current file +3. Report any hard-coded colors, spacing, typography, or other values +4. Suggest the correct Optics token replacements + +If no code is provided, ask the user to select code or specify a file path. diff --git a/editor-configs/cursor-mcp.json b/editor-configs/cursor-mcp.json new file mode 100644 index 0000000..b3ef8ac --- /dev/null +++ b/editor-configs/cursor-mcp.json @@ -0,0 +1,9 @@ +{ + "mcpServers": { + "optics": { + "command": "npx", + "args": ["-y", "@rolemodel/optics-mcp"], + "env": {} + } + } +} diff --git a/package.json b/package.json index 10dc0ca..fb25ab4 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,7 @@ "prepare": "npm run build", "watch": "npx tsc --watch", "start": "node dist/index.js", - "test": "node dist/test.js", - "test:interactive": "npm run build && node dist/interactive-client.js" + "test": "node dist/test.js" }, "keywords": [ "mcp", @@ -53,10 +52,8 @@ "dist", "README.md", "LICENSE", - "docs/SYSTEM_OVERVIEW.md", - "docs/AI_IMPROVEMENTS.md", - "docs/WARP.md", - "docs/TOOLS.md", - "docs/EXAMPLES.md" + "docs", + "editor-configs", + ".claude/commands" ] } diff --git a/src/index.ts b/src/index.ts index 62c88b9..a5ce2d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,7 @@ import { checkTokenContrast, formatContrastResult } from './tools/accessibility. import { suggestTokenMigration, formatMigrationSuggestions } from './tools/migration.js'; import { generateComponentScaffold, formatScaffoldOutput } from './tools/scaffold.js'; import { generateStickerSheet, formatStickerSheet } from './tools/sticker-sheet.js'; +import { getRecipe, searchRecipes, formatRecipe, formatRecipeList } from './tools/recipes.js'; // Resources import * as systemOverview from './resources/system-overview.js'; @@ -143,7 +144,9 @@ const prompts = [ accessibleColorComboPrompt, designReviewPrompt, explainTokenSystemPrompt, - getTokenReferencePrompt + getTokenReferencePrompt, + configureIconsPrompt, + useRecipePrompt ] prompts.forEach((prompt) => { diff --git a/src/prompts/configure-icons.ts b/src/prompts/configure-icons.ts new file mode 100644 index 0000000..fda73dc --- /dev/null +++ b/src/prompts/configure-icons.ts @@ -0,0 +1,74 @@ +import { z } from 'zod' +import { readResourceFile } from "../_internal/resource-path.js" +import { iconLibraries, suggestLibrary, formatLibraryComparison, formatIconConfig } from '../tools/icons.js' + +type ConfigureIconsPromptArgs = { + library?: string + needsFill?: boolean + needsWeight?: boolean + needsEmphasis?: boolean + needsDuotone?: boolean + preferSmallBundle?: boolean +} + +export const inputSchema = { + library: z.string().optional().describe('Icon library name (material, phosphor, tabler, feather, lucide)'), + needsFill: z.boolean().optional().describe('Requires fill/outlined toggle'), + needsWeight: z.boolean().optional().describe('Requires weight variations'), + needsEmphasis: z.boolean().optional().describe('Requires emphasis variations'), + needsDuotone: z.boolean().optional().describe('Requires duotone icons'), + preferSmallBundle: z.boolean().optional().describe('Prefer smaller bundle size'), +} + +export const metadata = { + name: "configure-icons", + title: "Configure Icons", + description: "Select and configure an icon library for your Optics project", + role: "user", +} + +export async function handler(args: ConfigureIconsPromptArgs) { + // If specific library requested, return its config + if (args.library) { + const lib = iconLibraries.find(l => + l.name.toLowerCase().includes(args.library!.toLowerCase()) || + l.prefix.toLowerCase().includes(args.library!.toLowerCase()) + ) + + if (lib) { + return formatIconConfig({ + library: lib.name, + name: 'settings' // example icon + }) + } + } + + // If requirements specified, suggest libraries + if (args.needsFill || args.needsWeight || args.needsEmphasis || args.needsDuotone || args.preferSmallBundle) { + const suggestions = suggestLibrary({ + needsFill: args.needsFill, + needsWeight: args.needsWeight, + needsEmphasis: args.needsEmphasis, + needsDuotone: args.needsDuotone, + preferSmallBundle: args.preferSmallBundle, + }) + + if (suggestions.length === 0) { + return `No icon libraries match all your requirements.\n\n${formatLibraryComparison()}` + } + + let output = `## Recommended Libraries\n\n` + for (const lib of suggestions) { + output += `### ${lib.name}\n` + output += `- Import: \`${lib.import}\`\n` + output += `- Usage: \`${lib.usage}\`\n` + if (lib.notes) output += `- Notes: ${lib.notes}\n` + output += `\n` + } + return output + } + + // Default: return full comparison from prompt template + const promptTemplate = await readResourceFile("prompts/configure-icons-prompt.md") + return promptTemplate +} diff --git a/src/prompts/use-recipe.ts b/src/prompts/use-recipe.ts new file mode 100644 index 0000000..e6fe6e8 --- /dev/null +++ b/src/prompts/use-recipe.ts @@ -0,0 +1,47 @@ +import { z } from 'zod' +import { getRecipe, searchRecipes, formatRecipe, formatRecipeList, getRecipeCategories } from '../tools/recipes.js' +import { readResourceFile } from "../_internal/resource-path.js" + +type UseRecipePromptArgs = { + slug?: string + category?: string + query?: string +} + +export const inputSchema = { + slug: z.string().optional().describe('Specific recipe slug to retrieve'), + category: z.string().optional().describe('Filter by category (layout, sidebar, header, component)'), + query: z.string().optional().describe('Search term for recipe name or description'), +} + +export const metadata = { + name: "use-recipe", + title: "Use Recipe", + description: "Get Optics customization recipes with CSS and HTML patterns", + role: "user", +} + +export async function handler(args: UseRecipePromptArgs) { + // If specific recipe requested + if (args.slug) { + const recipe = getRecipe(args.slug) + + if (recipe) { + return formatRecipe(recipe) + } + + // Recipe not found - show available + const allRecipes = searchRecipes() + return `Recipe not found: ${args.slug}\n\n${formatRecipeList(allRecipes)}` + } + + // If searching/filtering + if (args.category || args.query) { + const results = searchRecipes(args.query, args.category) + return formatRecipeList(results) + } + + // Default: return overview from template + const promptTemplate = await readResourceFile("prompts/use-recipe-prompt.md") + return promptTemplate +} diff --git a/src/resources/prompts/configure-icons-prompt.md b/src/resources/prompts/configure-icons-prompt.md new file mode 100644 index 0000000..11f5a17 --- /dev/null +++ b/src/resources/prompts/configure-icons-prompt.md @@ -0,0 +1,47 @@ +# Configure Icons + +Select and configure an icon library for your Optics project. + +## Available Libraries + +**Material Symbols** (default) +- Full variable font: fill, weight, emphasis, size +- Import: `@import '@rolemodel/optics/dist/css/core/fonts';` +- Usage: `settings` + +**Phosphor** +- Supports: fill, weight, size, duotone +- Import: `@import '@rolemodel/optics/dist/css/addons/fonts/phosphor_icons';` +- Usage: `` + +**Tabler** +- Supports: size, filled (via `.ti-{name}-filled`) +- Import: `@import '@rolemodel/optics/dist/css/addons/fonts/tabler_icons';` +- Usage: `` + +**Feather** +- Supports: size only +- Import: `@import '@rolemodel/optics/dist/css/addons/fonts/feather_icons';` +- Usage: `` + +**Lucide** +- Supports: size only (larger library than Feather) +- Import: `@import '@rolemodel/optics/dist/css/addons/fonts/lucide_icons';` +- Usage: `` + +## Modifiers + +- Size: `.icon--small`, `.icon--medium`, `.icon--large`, `.icon--x-large` +- Fill: `.icon--filled`, `.icon--outlined` +- Weight: `.icon--weight-light`, `.icon--weight-normal`, `.icon--weight-semi-bold`, `.icon--weight-bold` +- Emphasis: `.icon--low-emphasis`, `.icon--normal-emphasis`, `.icon--high-emphasis` + +## Selection Guide + +| Need | Recommended | +|------|-------------| +| Full customization | Material Symbols | +| Duotone icons | Phosphor | +| Clean minimal look | Tabler or Feather | +| Largest icon set | Lucide | +| Smallest bundle | Feather | diff --git a/src/resources/prompts/use-recipe-prompt.md b/src/resources/prompts/use-recipe-prompt.md new file mode 100644 index 0000000..de43bb7 --- /dev/null +++ b/src/resources/prompts/use-recipe-prompt.md @@ -0,0 +1,88 @@ +# Optics Recipes + +Optics provides real-world customization recipes showing how to extend and customize components for specific use cases. + +## Available Categories + +- **sidebar** - Custom sidebar configurations with brand colors, drawer modes, and navigation patterns +- **layout** - App shell layouts combining sidebar, header, and content areas +- **header** - Header patterns with aligned sections and navigation +- **component** - Custom component variations and patterns + +## Available Recipes + +### Sidebar + +- **sidebar-domains** - Domain registrar sidebar with wide drawer, no border radius buttons, and custom footer +- **sidebar-16six** - Dark purple sidebar with custom brand colors for performance management software + +### Layout + +- **layout-app-shell** - Standard app layout with sidebar, header, and main content area using CSS Grid + +### Header + +- **aligned-header** - Header with aligned left/center/right sections using CSS Grid + +## How to Use + +Each recipe includes: + +1. **Tokens Used** - Design tokens required for the customization +2. **CSS** - Scoped CSS that extends Optics base styles +3. **HTML** - Example markup structure + +### Example Usage + +To get a specific recipe with full CSS and HTML: + +``` +Use get_recipe with slug "sidebar-domains" +``` + +To search by category: + +``` +Use search_recipes with category "sidebar" +``` + +## Key Patterns + +### Scoping Customizations + +Recipes use modifier classes to scope customizations: + +```css +.sidebar { + &.sidebar--domains { + /* Domain-specific customizations */ + } +} +``` + +### Token Overrides + +Recipes show how to override private tokens (prefixed with `--_op-`): + +```css +.sidebar--custom { + --_op-sidebar-drawer-width: 28rem; + --_op-sidebar-background-color: hsl(256deg 66% 15%); +} +``` + +### Composing with Optics Classes + +Recipes combine with existing Optics utilities: + +```html + +``` + +## Docs Reference + +Full recipe documentation: https://docs.optics.rolemodel.design/?path=/docs/recipes-custom-sidebar--docs diff --git a/src/tools/icons.ts b/src/tools/icons.ts new file mode 100644 index 0000000..106a656 --- /dev/null +++ b/src/tools/icons.ts @@ -0,0 +1,292 @@ +/** + * Icon Tools for Optics Design System + * Supports multiple icon libraries with varying feature sets + */ + +export interface IconLibrary { + name: string; + prefix: string; + import: string; + supports: { + fill: boolean; + weight: boolean; + emphasis: boolean; + size: boolean; + duotone?: boolean; + }; + usage: string; + notes?: string; +} + +export interface IconConfig { + library: string; + name: string; + size?: 'small' | 'medium' | 'large' | 'x-large'; + weight?: 'light' | 'normal' | 'semi-bold' | 'bold' | 'thin'; + fill?: boolean; + emphasis?: 'low' | 'normal' | 'high'; +} + +/** + * Supported icon libraries in Optics + */ +export const iconLibraries: IconLibrary[] = [ + { + name: 'Material Symbols', + prefix: 'material-symbols-outlined', + import: `@import '@rolemodel/optics/dist/css/core/fonts';`, + supports: { fill: true, weight: true, emphasis: true, size: true }, + usage: 'settings', + notes: 'Default icon library. Full variable font support with fill, weight, emphasis, and size.' + }, + { + name: 'Phosphor', + prefix: 'ph', + import: `@import '@rolemodel/optics/dist/css/addons/fonts/phosphor_icons';`, + supports: { fill: true, weight: true, emphasis: false, size: true, duotone: true }, + usage: '', + notes: 'Supports .ph-duotone for two-tone icons. Adds .icon--weight-thin. Does not support .icon--weight-semi-bold or emphasis.' + }, + { + name: 'Tabler', + prefix: 'ti', + import: `@import '@rolemodel/optics/dist/css/addons/fonts/tabler_icons';`, + supports: { fill: true, weight: false, emphasis: false, size: true }, + usage: '', + notes: 'For filled icons, use .ti-{name}-filled instead of .icon--filled modifier.' + }, + { + name: 'Feather', + prefix: 'fi', + import: `@import '@rolemodel/optics/dist/css/addons/fonts/feather_icons';`, + supports: { fill: false, weight: false, emphasis: false, size: true }, + usage: '', + notes: 'Minimal feature set. CDN does not include all icons. Consider Lucide for more icons.' + }, + { + name: 'Lucide', + prefix: 'li', + import: `@import '@rolemodel/optics/dist/css/addons/fonts/lucide_icons';`, + supports: { fill: false, weight: false, emphasis: false, size: true }, + usage: '', + notes: 'Fork of Feather with more icons. Larger library but same limited modifier support.' + } +]; + +/** + * Size modifiers available for icons + */ +export const sizeModifiers = { + small: 'icon--small', + medium: 'icon--medium', + large: 'icon--large', + 'x-large': 'icon--x-large' +}; + +/** + * Weight modifiers (availability varies by library) + */ +export const weightModifiers = { + thin: 'icon--weight-thin', // Phosphor only + light: 'icon--weight-light', + normal: 'icon--weight-normal', + 'semi-bold': 'icon--weight-semi-bold', // Not Phosphor + bold: 'icon--weight-bold' +}; + +/** + * Emphasis modifiers (Material Symbols only) + */ +export const emphasisModifiers = { + low: 'icon--low-emphasis', + normal: 'icon--normal-emphasis', + high: 'icon--high-emphasis' +}; + +/** + * Get library by name + */ +export function getLibrary(name: string): IconLibrary | undefined { + return iconLibraries.find(lib => + lib.name.toLowerCase() === name.toLowerCase() || + lib.prefix === name.toLowerCase() + ); +} + +/** + * Generate HTML for an icon + */ +export function generateIconHTML(config: IconConfig): string { + const library = getLibrary(config.library); + if (!library) { + return ``; + } + + const classes = ['icon']; + + // Add library-specific prefix + if (library.prefix === 'material-symbols-outlined') { + classes.push('material-symbols-outlined'); + } else { + classes.push(library.prefix); + classes.push(`${library.prefix}-${config.name}`); + } + + // Add size modifier + if (config.size && library.supports.size) { + classes.push(sizeModifiers[config.size]); + } + + // Add weight modifier + if (config.weight && library.supports.weight) { + classes.push(weightModifiers[config.weight]); + } + + // Add fill modifier + if (config.fill && library.supports.fill) { + if (library.prefix === 'ti') { + // Tabler uses different pattern for filled + classes[classes.length - 1] = `ti-${config.name}-filled`; + } else { + classes.push('icon--filled'); + } + } + + // Add emphasis modifier + if (config.emphasis && library.supports.emphasis) { + classes.push(emphasisModifiers[config.emphasis]); + } + + // Generate HTML based on library + if (library.prefix === 'material-symbols-outlined') { + return `${config.name}`; + } else { + return ``; + } +} + +/** + * Generate import statement for a library + */ +export function generateImport(libraryName: string): string { + const library = getLibrary(libraryName); + if (!library) { + return `/* Unknown library: ${libraryName} */`; + } + + if (library.prefix === 'material-symbols-outlined') { + return `/* Material Symbols (default - included with Optics) */ +${library.import}`; + } + + return `/* ${library.name} Icons */ +@import '@rolemodel/optics'; +${library.import}`; +} + +/** + * Format library comparison for display + */ +export function formatLibraryComparison(): string { + let output = `# Optics Icon Libraries + +| Library | Prefix | Fill | Weight | Emphasis | Size | Notes | +|---------|--------|------|--------|----------|------|-------| +`; + + for (const lib of iconLibraries) { + const s = lib.supports; + output += `| ${lib.name} | \`.${lib.prefix}\` | ${s.fill ? '✓' : '✗'} | ${s.weight ? '✓' : '✗'} | ${s.emphasis ? '✓' : '✗'} | ${s.size ? '✓' : '✗'} | ${lib.notes || ''} |\n`; + } + + output += ` +## Usage Examples + +`; + + for (const lib of iconLibraries) { + output += `### ${lib.name} + +\`\`\`css +${lib.import} +\`\`\` + +\`\`\`html +${lib.usage} +\`\`\` + +`; + } + + return output; +} + +/** + * Format icon configuration output + */ +export function formatIconConfig(config: IconConfig): string { + const library = getLibrary(config.library); + if (!library) { + return `Unknown library: ${config.library}\n\nAvailable libraries: ${iconLibraries.map(l => l.name).join(', ')}`; + } + + const html = generateIconHTML(config); + const importStatement = generateImport(config.library); + + let output = `## ${library.name} Icon: ${config.name} + +### Import + +\`\`\`css +${importStatement} +\`\`\` + +### HTML + +\`\`\`html +${html} +\`\`\` + +### Configuration + +| Option | Value | Supported | +|--------|-------|-----------| +| Size | ${config.size || 'medium (default)'} | ${library.supports.size ? '✓' : '✗'} | +| Weight | ${config.weight || 'normal (default)'} | ${library.supports.weight ? '✓' : '✗'} | +| Fill | ${config.fill ? 'filled' : 'outlined'} | ${library.supports.fill ? '✓' : '✗'} | +| Emphasis | ${config.emphasis || 'normal (default)'} | ${library.supports.emphasis ? '✓' : '✗'} | +`; + + if (library.notes) { + output += `\n### Notes\n\n${library.notes}`; + } + + return output; +} + +/** + * Suggest best library for use case + */ +export function suggestLibrary(requirements: { + needsFill?: boolean; + needsWeight?: boolean; + needsEmphasis?: boolean; + needsDuotone?: boolean; + preferSmallBundle?: boolean; +}): IconLibrary[] { + return iconLibraries.filter(lib => { + if (requirements.needsFill && !lib.supports.fill) return false; + if (requirements.needsWeight && !lib.supports.weight) return false; + if (requirements.needsEmphasis && !lib.supports.emphasis) return false; + if (requirements.needsDuotone && !lib.supports.duotone) return false; + return true; + }).sort((a, b) => { + // Prefer libraries with more features unless small bundle needed + if (requirements.preferSmallBundle) { + const aFeatures = Object.values(a.supports).filter(Boolean).length; + const bFeatures = Object.values(b.supports).filter(Boolean).length; + return aFeatures - bFeatures; + } + return 0; + }); +} diff --git a/src/tools/recipes.ts b/src/tools/recipes.ts new file mode 100644 index 0000000..040da7a --- /dev/null +++ b/src/tools/recipes.ts @@ -0,0 +1,408 @@ +/** + * Recipes Tools for Optics Design System + * Provides real-world customization examples and patterns + */ + +export interface Recipe { + name: string; + slug: string; + description: string; + category: 'layout' | 'sidebar' | 'header' | 'component'; + docsUrl: string; + tokens: string[]; + css: string; + html: string; +} + +/** + * Available Optics recipes + */ +export const recipes: Recipe[] = [ + { + name: 'Custom Sidebar', + slug: 'sidebar-domains', + description: 'A sidebar customization for domain registrar apps with wide drawer, no border radius buttons, and custom footer', + category: 'sidebar', + docsUrl: 'https://docs.optics.rolemodel.design/?path=/docs/recipes-custom-sidebar--docs', + tokens: [ + '--_op-sidebar-drawer-width', + '--_op-sidebar-content-spacing', + '--_op-sidebar-content-item-spacing', + '--op-color-primary-plus-five', + '--op-color-primary-on-plus-five', + '--op-color-neutral-plus-six', + '--op-color-neutral-on-plus-six', + '--op-radius-pill', + '--op-space-small', + '--op-space-large' + ], + css: `/* Domains Sidebar Example */ +.sidebar { + &.sidebar--domains { + --_op-sidebar-drawer-width: 28rem; + --_op-sidebar-content-spacing: 0; + --_op-sidebar-content-item-spacing: 0; + box-shadow: none; + + .btn { + border-radius: 0; + + &.btn--no-border { + box-shadow: none; + + &.btn--active { + background-color: var(--op-color-primary-plus-five); + color: var(--op-color-primary-on-plus-five); + } + + &:hover:not(.btn--active) { + background-color: var(--op-color-neutral-plus-six); + box-shadow: none; + color: var(--op-color-neutral-on-plus-six); + } + } + + &.btn--pill-right { + border-radius: 0 var(--op-radius-pill) var(--op-radius-pill) 0; + } + } + + .sidebar__footer { + display: flex; + align-items: center; + gap: var(--op-space-small); + padding-inline-start: var(--op-space-large); + } + } +}`, + html: `` + }, + { + name: 'Custom Sidebar - 16Six Performance', + slug: 'sidebar-16six', + description: 'A dark purple sidebar with custom brand colors, rail and drawer modes, suitable for performance management software', + category: 'sidebar', + docsUrl: 'https://docs.optics.rolemodel.design/?path=/docs/recipes-custom-sidebar--docs', + tokens: [ + '--_op-sidebar-background-color', + '--_op-sidebar-text-color', + '--_op-sidebar-border-color', + '--_op-sidebar-rail-width', + '--_op-sidebar-drawer-width', + '--_op-sidebar-drawer-brand-width', + '--_op-sidebar-brand-spacing', + '--_op-sidebar-content-item-spacing', + '--_op-sidebar-spacing', + '--op-space-3x-large', + '--op-space-medium', + '--op-space-x-small', + '--op-font-small' + ], + css: `/* 16Six Sidebar Example */ +.icon--rotated-135 { + rotate: 135deg; +} + +.icon--rotated-90 { + rotate: 90deg; +} + +.sidebar { + .sidebar__brand { + justify-content: center; + + .sidebar__brand-label { + display: none; + } + } + + &.sidebar--16six { + --_op-sidebar-background-color: hsl(256deg 66% 15%); + --_op-sidebar-text-color: hsl(26deg 100% 95%); + --_op-sidebar-border-color: hsl(26deg 100% 95%); + --_op-sidebar-rail-width: 6.4rem; + --_op-sidebar-drawer-width: 22.4rem; + --_op-sidebar-drawer-brand-width: calc(var(--op-space-3x-large) + (2 * var(--op-space-medium))); + --_op-sidebar-brand-spacing: var(--op-space-medium) var(--op-space-x-small); + --_op-sidebar-content-item-spacing: var(--op-space-x-small); + --_op-sidebar-spacing: 0 0 var(--op-space-x-small); + + .sidebar__brand { + display: flex; + margin: 0; + color: inherit; + gap: var(--op-space-medium); + text-decoration: none; + + svg { + inline-size: var(--op-space-3x-large); + block-size: var(--op-space-3x-large); + } + + .sidebar__brand-label { + display: flex; + flex-direction: column; + font-size: var(--op-font-small); + } + } + + .btn { + &.btn--no-border { + background-color: transparent; + box-shadow: none; + color: var(--_op-sidebar-text-color); + + &.btn--active, + &:hover { + background-color: hsl(256deg 23% 32%); + } + } + } + } +}`, + html: `` + }, + { + name: 'Layout - App Shell', + slug: 'layout-app-shell', + description: 'Standard app layout with sidebar, header, and main content area', + category: 'layout', + docsUrl: 'https://docs.optics.rolemodel.design/?path=/docs/recipes-layout--docs', + tokens: [ + '--op-color-neutral-plus-eight', + '--op-color-neutral-on-plus-eight', + '--op-space-medium', + '--op-space-large' + ], + css: `/* App Shell Layout */ +.app-shell { + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto 1fr; + min-height: 100vh; +} + +.app-shell__sidebar { + grid-row: 1 / -1; +} + +.app-shell__header { + grid-column: 2; +} + +.app-shell__main { + grid-column: 2; + padding: var(--op-space-large); + background-color: var(--op-color-neutral-plus-eight); + color: var(--op-color-neutral-on-plus-eight); +}`, + html: `
+ +
+ +
+
+ +
+
` + }, + { + name: 'Aligned Header', + slug: 'aligned-header', + description: 'Header with aligned left/center/right sections', + category: 'header', + docsUrl: 'https://docs.optics.rolemodel.design/?path=/docs/recipes-aligned-header--docs', + tokens: [ + '--op-space-medium', + '--op-space-small' + ], + css: `/* Aligned Header */ +.header-aligned { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: var(--op-space-medium); + padding: var(--op-space-small) var(--op-space-medium); +} + +.header-aligned__start { + justify-self: start; +} + +.header-aligned__center { + justify-self: center; +} + +.header-aligned__end { + justify-self: end; +}`, + html: `
+
+ Logo +
+ +
+ +
+
` + } +]; + +/** + * Get recipe by slug + */ +export function getRecipe(slug: string): Recipe | undefined { + return recipes.find(r => r.slug === slug); +} + +/** + * Search recipes by category or keyword + */ +export function searchRecipes(query?: string, category?: string): Recipe[] { + let results = recipes; + + if (category) { + results = results.filter(r => r.category === category); + } + + if (query) { + const lowerQuery = query.toLowerCase(); + results = results.filter(r => + r.name.toLowerCase().includes(lowerQuery) || + r.description.toLowerCase().includes(lowerQuery) || + r.slug.toLowerCase().includes(lowerQuery) + ); + } + + return results; +} + +/** + * List all recipe categories + */ +export function getRecipeCategories(): string[] { + return Array.from(new Set(recipes.map(r => r.category))); +} + +/** + * Format recipe for display + */ +export function formatRecipe(recipe: Recipe): string { + return `## ${recipe.name} + +${recipe.description} + +**Category:** ${recipe.category} +**Docs:** ${recipe.docsUrl} + +### Tokens Used + +${recipe.tokens.map(t => `- \`${t}\``).join('\n')} + +### CSS + +\`\`\`css +${recipe.css} +\`\`\` + +### HTML + +\`\`\`html +${recipe.html} +\`\`\` +`; +} + +/** + * Format recipe list for display + */ +export function formatRecipeList(recipeList: Recipe[]): string { + if (recipeList.length === 0) { + return 'No recipes found.\n\nAvailable categories: ' + getRecipeCategories().join(', '); + } + + let output = `# Optics Recipes (${recipeList.length})\n\n`; + + const byCategory: Record = {}; + for (const recipe of recipeList) { + if (!byCategory[recipe.category]) { + byCategory[recipe.category] = []; + } + byCategory[recipe.category].push(recipe); + } + + for (const [category, categoryRecipes] of Object.entries(byCategory)) { + output += `## ${category.charAt(0).toUpperCase() + category.slice(1)}\n\n`; + for (const recipe of categoryRecipes) { + output += `- **${recipe.name}** (\`${recipe.slug}\`)\n ${recipe.description}\n\n`; + } + } + + output += `\nUse \`get_recipe\` with a slug to get full details.`; + return output; +} diff --git a/test-prompts/scaffold-test.md b/test-prompts/scaffold-test.md new file mode 100644 index 0000000..c7fd0ae --- /dev/null +++ b/test-prompts/scaffold-test.md @@ -0,0 +1,139 @@ +# MCP Scaffolding Test Prompt + +Use this prompt to test the optics-mcp server. Run it **with** and **without** the MCP enabled to compare results. + +--- + +## Test Prompt + +``` +Create a metrics dashboard using the Optics design system. + +## Context +- **Design System**: Optics by RoleModel Software +- **Docs**: https://docs.optics.rolemodel.design +- **Token Prefix**: `--op-` (e.g., `--op-color-primary-base`, `--op-space-medium`) +- **Tech Stack**: Vite + vanilla HTML/CSS/JS + +## Requirements + +### Layout +- Sidebar navigation with logo, nav items, and user avatar at bottom +- Main content area with header and grid of cards +- Use Optics layout utilities (stack, cluster, split, container) + +### Components needed +- Sidebar with active state styling +- 4 metric cards showing: Users, Revenue, Orders, Conversion Rate +- Each card has: icon, label, value, and trend indicator (up/down arrow with percentage) +- Simple bar chart placeholder area +- Data table with 5 rows showing recent orders + +### Theming +- Use a custom primary color: `#6366f1` (indigo) +- Configure the HSL base tokens to enable the full color scale +- Support automatic dark mode via `prefers-color-scheme` + +### Icons +- Use the Phosphor icon library +- Icons needed: house, users, currency-dollar, shopping-cart, chart-line, arrow-up, arrow-down, gear, sign-out + +## Deliverables +1. Complete Vite project structure +2. HTML using Optics component classes and layout utilities +3. CSS that properly overrides the primary color HSL tokens +4. Phosphor icons import configured correctly +5. Should run with `npm run dev` +``` + +--- + +## Success Criteria + +### WITHOUT MCP (expect failures) + +AI will likely: +- ❌ Invent layout classes like `.op-sidebar`, `.op-grid`, `.dashboard-container` +- ❌ Use wrong token names: `--op-primary`, `--op-color-indigo-500`, `--op-spacing-4` +- ❌ Hardcode the indigo color instead of setting HSL base tokens +- ❌ Miss the HSL theming system entirely (`--op-color-primary-h`, `-s`, `-l`) +- ❌ Wrong icon setup: use wrong prefix, wrong import path, or invent classes +- ❌ Not use `-on-` tokens for text on colored backgrounds +- ❌ Invent component classes that don't exist in Optics + +### WITH MCP (expect success) + +AI should: +- ✅ Use real layout utilities: `.stack`, `.cluster`, `.split`, `.container` +- ✅ Set HSL tokens correctly: `--op-color-primary-h: 239; --op-color-primary-s: 84%; --op-color-primary-l: 67%;` +- ✅ Use scale tokens: `--op-color-primary-base`, `--op-color-primary-plus-five` +- ✅ Use on-tokens: `--op-color-primary-on-base` for text on primary backgrounds +- ✅ Correct Phosphor setup: `@import '@rolemodel/optics/dist/css/addons/fonts/phosphor_icons';` +- ✅ Correct icon markup: `` +- ✅ Use real spacing: `--op-space-small`, `--op-space-medium`, `--op-space-large` +- ✅ Use real component patterns from Optics docs + +--- + +## Scoring Rubric + +| Category | Criterion | Points | +|----------|-----------|--------| +| **Theming** | Sets HSL base tokens (h, s, l) correctly | 15 | +| | Uses color scale tokens (base, plus-*, minus-*) | 10 | +| | Uses -on- tokens for text on backgrounds | 10 | +| | Dark mode works via color-scheme | 5 | +| **Layout** | Uses real Optics layout utilities | 10 | +| | Correct container/spacing usage | 5 | +| **Icons** | Correct Phosphor import path | 10 | +| | Correct icon markup (`icon ph ph-*`) | 10 | +| | Uses available modifiers correctly | 5 | +| **Components** | Card structure follows Optics patterns | 5 | +| | Table structure follows Optics patterns | 5 | +| | Sidebar follows Optics patterns | 5 | +| **Tokens** | No hallucinated token names | 5 | +| | Uses correct spacing tokens | 5 | +| | Uses correct typography tokens | 5 | +| **Total** | | **100** | + +--- + +## Expected Token Usage + +### Theming (converting #6366f1 to HSL) + +```css +:root { + --op-color-primary-h: 239; + --op-color-primary-s: 84%; + --op-color-primary-l: 67%; +} +``` + +### Icon Import + +```css +@import '@rolemodel/optics'; +@import '@rolemodel/optics/dist/css/addons/fonts/phosphor_icons'; +``` + +### Icon Markup + +```html + + + + +``` + +### Layout Structure + +```html +
+ +
+
...
+
...
+
+
+``` From b7d824720c90eb19636094452bdb21d1ac6b4ae5 Mon Sep 17 00:00:00 2001 From: Dallas Peters Date: Thu, 5 Feb 2026 10:45:36 -0600 Subject: [PATCH 02/28] Remove MCP Scaffolding Test Prompt file from the repository --- test-prompts/scaffold-test.md | 139 ---------------------------------- 1 file changed, 139 deletions(-) delete mode 100644 test-prompts/scaffold-test.md diff --git a/test-prompts/scaffold-test.md b/test-prompts/scaffold-test.md deleted file mode 100644 index c7fd0ae..0000000 --- a/test-prompts/scaffold-test.md +++ /dev/null @@ -1,139 +0,0 @@ -# MCP Scaffolding Test Prompt - -Use this prompt to test the optics-mcp server. Run it **with** and **without** the MCP enabled to compare results. - ---- - -## Test Prompt - -``` -Create a metrics dashboard using the Optics design system. - -## Context -- **Design System**: Optics by RoleModel Software -- **Docs**: https://docs.optics.rolemodel.design -- **Token Prefix**: `--op-` (e.g., `--op-color-primary-base`, `--op-space-medium`) -- **Tech Stack**: Vite + vanilla HTML/CSS/JS - -## Requirements - -### Layout -- Sidebar navigation with logo, nav items, and user avatar at bottom -- Main content area with header and grid of cards -- Use Optics layout utilities (stack, cluster, split, container) - -### Components needed -- Sidebar with active state styling -- 4 metric cards showing: Users, Revenue, Orders, Conversion Rate -- Each card has: icon, label, value, and trend indicator (up/down arrow with percentage) -- Simple bar chart placeholder area -- Data table with 5 rows showing recent orders - -### Theming -- Use a custom primary color: `#6366f1` (indigo) -- Configure the HSL base tokens to enable the full color scale -- Support automatic dark mode via `prefers-color-scheme` - -### Icons -- Use the Phosphor icon library -- Icons needed: house, users, currency-dollar, shopping-cart, chart-line, arrow-up, arrow-down, gear, sign-out - -## Deliverables -1. Complete Vite project structure -2. HTML using Optics component classes and layout utilities -3. CSS that properly overrides the primary color HSL tokens -4. Phosphor icons import configured correctly -5. Should run with `npm run dev` -``` - ---- - -## Success Criteria - -### WITHOUT MCP (expect failures) - -AI will likely: -- ❌ Invent layout classes like `.op-sidebar`, `.op-grid`, `.dashboard-container` -- ❌ Use wrong token names: `--op-primary`, `--op-color-indigo-500`, `--op-spacing-4` -- ❌ Hardcode the indigo color instead of setting HSL base tokens -- ❌ Miss the HSL theming system entirely (`--op-color-primary-h`, `-s`, `-l`) -- ❌ Wrong icon setup: use wrong prefix, wrong import path, or invent classes -- ❌ Not use `-on-` tokens for text on colored backgrounds -- ❌ Invent component classes that don't exist in Optics - -### WITH MCP (expect success) - -AI should: -- ✅ Use real layout utilities: `.stack`, `.cluster`, `.split`, `.container` -- ✅ Set HSL tokens correctly: `--op-color-primary-h: 239; --op-color-primary-s: 84%; --op-color-primary-l: 67%;` -- ✅ Use scale tokens: `--op-color-primary-base`, `--op-color-primary-plus-five` -- ✅ Use on-tokens: `--op-color-primary-on-base` for text on primary backgrounds -- ✅ Correct Phosphor setup: `@import '@rolemodel/optics/dist/css/addons/fonts/phosphor_icons';` -- ✅ Correct icon markup: `` -- ✅ Use real spacing: `--op-space-small`, `--op-space-medium`, `--op-space-large` -- ✅ Use real component patterns from Optics docs - ---- - -## Scoring Rubric - -| Category | Criterion | Points | -|----------|-----------|--------| -| **Theming** | Sets HSL base tokens (h, s, l) correctly | 15 | -| | Uses color scale tokens (base, plus-*, minus-*) | 10 | -| | Uses -on- tokens for text on backgrounds | 10 | -| | Dark mode works via color-scheme | 5 | -| **Layout** | Uses real Optics layout utilities | 10 | -| | Correct container/spacing usage | 5 | -| **Icons** | Correct Phosphor import path | 10 | -| | Correct icon markup (`icon ph ph-*`) | 10 | -| | Uses available modifiers correctly | 5 | -| **Components** | Card structure follows Optics patterns | 5 | -| | Table structure follows Optics patterns | 5 | -| | Sidebar follows Optics patterns | 5 | -| **Tokens** | No hallucinated token names | 5 | -| | Uses correct spacing tokens | 5 | -| | Uses correct typography tokens | 5 | -| **Total** | | **100** | - ---- - -## Expected Token Usage - -### Theming (converting #6366f1 to HSL) - -```css -:root { - --op-color-primary-h: 239; - --op-color-primary-s: 84%; - --op-color-primary-l: 67%; -} -``` - -### Icon Import - -```css -@import '@rolemodel/optics'; -@import '@rolemodel/optics/dist/css/addons/fonts/phosphor_icons'; -``` - -### Icon Markup - -```html - - - - -``` - -### Layout Structure - -```html -
- -
-
...
-
...
-
-
-``` From 774c3d8839009a2912e4efa0b6fa142c87f8b4c9 Mon Sep 17 00:00:00 2001 From: Dallas Peters Date: Thu, 5 Feb 2026 11:02:47 -0600 Subject: [PATCH 03/28] Fix test:interactive --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index fb25ab4..b834bc6 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "prepare": "npm run build", "watch": "npx tsc --watch", "start": "node dist/index.js", - "test": "node dist/test.js" + "test": "node dist/test.js", + "test:interactive": "npm run build && node dist/interactive-client.js" }, "keywords": [ "mcp", From ce143cf9adb576723616c640c140c66763085d88 Mon Sep 17 00:00:00 2001 From: Dallas Peters Date: Thu, 5 Feb 2026 13:01:27 -0600 Subject: [PATCH 04/28] Remove .claude specific commands. After further research promps should show up in the IDE as commands. --- .claude/commands/check-contrast.md | 18 ---------- .claude/commands/create-component.md | 18 ---------- .claude/commands/find-token.md | 16 --------- .claude/commands/icons.md | 29 ---------------- .claude/commands/migrate-css.md | 29 ---------------- .claude/commands/recipe.md | 52 ---------------------------- .claude/commands/review.md | 17 --------- .claude/commands/validate-optics.md | 12 ------- src/index.ts | 8 +++++ 9 files changed, 8 insertions(+), 191 deletions(-) delete mode 100644 .claude/commands/check-contrast.md delete mode 100644 .claude/commands/create-component.md delete mode 100644 .claude/commands/find-token.md delete mode 100644 .claude/commands/icons.md delete mode 100644 .claude/commands/migrate-css.md delete mode 100644 .claude/commands/recipe.md delete mode 100644 .claude/commands/review.md delete mode 100644 .claude/commands/validate-optics.md diff --git a/.claude/commands/check-contrast.md b/.claude/commands/check-contrast.md deleted file mode 100644 index 0500de9..0000000 --- a/.claude/commands/check-contrast.md +++ /dev/null @@ -1,18 +0,0 @@ -# Check Color Contrast - -Verify WCAG contrast ratio between two Optics color tokens. - -## Instructions - -1. Use the `check_contrast` tool from the optics-mcp server -2. Parse $ARGUMENTS for foreground and background token names -3. Report the contrast ratio and WCAG compliance (AA/AAA) - -## Usage - -``` -/check-contrast primary-on-base primary-base -/check-contrast neutral-on-plus-eight neutral-plus-eight -``` - -If tokens aren't found, suggest similar token names using `search_tokens`. diff --git a/.claude/commands/create-component.md b/.claude/commands/create-component.md deleted file mode 100644 index 8f70c97..0000000 --- a/.claude/commands/create-component.md +++ /dev/null @@ -1,18 +0,0 @@ -# Create Optics Component - -Generate a component styled with Optics design tokens. - -## Instructions - -Invoke the `create-themed-component` prompt from the optics-mcp server. - -**Arguments from $ARGUMENTS:** -- Component type (e.g., button, card, form, alert) -- Optional: variant (primary, secondary, danger) -- Optional: framework (react, vue, svelte, html) - -## Example - -``` -/create-component button primary react -``` diff --git a/.claude/commands/find-token.md b/.claude/commands/find-token.md deleted file mode 100644 index f86da7c..0000000 --- a/.claude/commands/find-token.md +++ /dev/null @@ -1,16 +0,0 @@ -# Find Optics Token - -Search for an Optics design token by name or category. - -## Instructions - -1. Use the `search_tokens` tool from the optics-mcp server -2. If $ARGUMENTS contains a category (color, spacing, typography, border, shadow), filter by category -3. Otherwise, search by name pattern -4. Return the matching tokens with their values and descriptions - -## Examples - -- `/find-token primary` → finds all tokens with "primary" in the name -- `/find-token spacing` → lists all spacing tokens -- `/find-token plus-five` → finds scale tokens at +5 level diff --git a/.claude/commands/icons.md b/.claude/commands/icons.md deleted file mode 100644 index 8961b98..0000000 --- a/.claude/commands/icons.md +++ /dev/null @@ -1,29 +0,0 @@ -# Configure Optics Icons - -Select and configure an icon library for your project. - -## Instructions - -Invoke the `configure-icons` prompt from the optics-mcp server. - -**Arguments from $ARGUMENTS:** -- Library name (material, phosphor, tabler, feather, lucide) -- Or requirements: "need fill", "need weight", "small bundle" - -## Available Libraries - -| Library | Fill | Weight | Emphasis | Size | Notes | -|---------|------|--------|----------|------|-------| -| Material Symbols | ✓ | ✓ | ✓ | ✓ | Default, full features | -| Phosphor | ✓ | ✓ | ✗ | ✓ | Has duotone option | -| Tabler | ✓ | ✗ | ✗ | ✓ | Clean, simple | -| Feather | ✗ | ✗ | ✗ | ✓ | Minimal, small | -| Lucide | ✗ | ✗ | ✗ | ✓ | Feather fork, more icons | - -## Examples - -``` -/icons phosphor -/icons need fill and duotone -/icons smallest bundle -``` diff --git a/.claude/commands/migrate-css.md b/.claude/commands/migrate-css.md deleted file mode 100644 index 0929050..0000000 --- a/.claude/commands/migrate-css.md +++ /dev/null @@ -1,29 +0,0 @@ -# Migrate to Optics Tokens - -Convert hard-coded CSS values to Optics design tokens. - -## Instructions - -Invoke the `migrate-to-tokens` prompt from the optics-mcp server. - -Pass the CSS code from $ARGUMENTS or current selection. - -## Example - -Input: -```css -.button { - background: #2563eb; - padding: 16px 24px; - border-radius: 4px; -} -``` - -Output: -```css -.button { - background: var(--op-color-primary-base); - padding: var(--op-space-medium) var(--op-space-x-large); - border-radius: var(--op-radius-medium); -} -``` diff --git a/.claude/commands/recipe.md b/.claude/commands/recipe.md deleted file mode 100644 index 8ea8bd5..0000000 --- a/.claude/commands/recipe.md +++ /dev/null @@ -1,52 +0,0 @@ -# Optics Recipe Command - -Use this command to get real-world customization patterns from Optics. - -## Usage - -Find a recipe that matches your customization needs using the `use-recipe` prompt. - -### Arguments - -- `slug` - Get a specific recipe (e.g., "sidebar-domains", "layout-app-shell") -- `category` - Filter by category: layout, sidebar, header, component -- `query` - Search by keyword - -### Examples - -Get sidebar customization recipe: -``` -/recipe slug:sidebar-domains -``` - -Browse all sidebar recipes: -``` -/recipe category:sidebar -``` - -Search for dark theme examples: -``` -/recipe query:dark -``` - -## Available Recipes - -### Sidebar -- `sidebar-domains` - Domain registrar with wide drawer and custom footer -- `sidebar-16six` - Dark purple performance management sidebar - -### Layout -- `layout-app-shell` - Standard app shell with sidebar, header, main area - -### Header -- `aligned-header` - Three-section header with CSS Grid alignment - -## What's Included - -Each recipe provides: - -1. **Description** - Use case and context -2. **Tokens Used** - Required Optics design tokens -3. **CSS** - Complete scoped stylesheet -4. **HTML** - Working markup example -5. **Docs Link** - Reference to full documentation diff --git a/.claude/commands/review.md b/.claude/commands/review.md deleted file mode 100644 index 4f2f10d..0000000 --- a/.claude/commands/review.md +++ /dev/null @@ -1,17 +0,0 @@ -# Design Review - -Review code for Optics design system compliance. - -## Instructions - -Invoke the `design-review` prompt from the optics-mcp server. - -Pass the code from $ARGUMENTS or current selection for review. - -## What it checks - -- Hard-coded values that should use tokens -- Proper token usage and naming -- Accessibility (color contrast, focus states) -- Consistency with Optics patterns -- Missing or incorrect tokens diff --git a/.claude/commands/validate-optics.md b/.claude/commands/validate-optics.md deleted file mode 100644 index 2d51e75..0000000 --- a/.claude/commands/validate-optics.md +++ /dev/null @@ -1,12 +0,0 @@ -# Validate Optics Token Usage - -Scan the current file or selection for hard-coded values that should use Optics tokens. - -## Instructions - -1. Use the `validate_token_usage` tool from the optics-mcp server -2. Pass the code from $ARGUMENTS or the current file -3. Report any hard-coded colors, spacing, typography, or other values -4. Suggest the correct Optics token replacements - -If no code is provided, ask the user to select code or specify a file path. diff --git a/src/index.ts b/src/index.ts index a5ce2d8..f4810c6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,6 +41,8 @@ import * as accessibleColorComboPrompt from './prompts/accessible-color-combo.js import * as designReviewPrompt from './prompts/design-review.js'; import * as explainTokenSystemPrompt from './prompts/explain-token-system.js'; import * as getTokenReferencePrompt from './prompts/get-token-reference.js'; +import * as configureIconsPrompt from './prompts/configure-icons.js'; +import * as useRecipePrompt from './prompts/use-recipe.js'; /** * Create and configure the MCP server @@ -48,6 +50,12 @@ import * as getTokenReferencePrompt from './prompts/get-token-reference.js'; const server = new McpServer({ name: 'optics-mcp', version: '0.1.0', +}, { + capabilities: { + tools: {}, + prompts: {}, + resources: {}, + } }); /** From fa51cf7329a9f1fb1c7ea6aeeb7602554bab1f38 Mon Sep 17 00:00:00 2001 From: Jeremy Walton Date: Thu, 5 Feb 2026 14:05:33 -0500 Subject: [PATCH 05/28] Extract get token tool to its own file (#19) --- src/index.ts | 52 ++++++++++++++++--------------------- src/tools/get-token-tool.ts | 30 +++++++++++++++++++++ src/tools/tool.ts | 46 ++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 29 deletions(-) create mode 100644 src/tools/get-token-tool.ts create mode 100644 src/tools/tool.ts diff --git a/src/index.ts b/src/index.ts index f4810c6..fcf20b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,6 +44,9 @@ import * as getTokenReferencePrompt from './prompts/get-token-reference.js'; import * as configureIconsPrompt from './prompts/configure-icons.js'; import * as useRecipePrompt from './prompts/use-recipe.js'; +// Tools +import GetTokenTool from './tools/get-token-tool.js'; + /** * Create and configure the MCP server */ @@ -184,45 +187,36 @@ prompts.forEach((prompt) => { ) }) - /** - * Tool: Get Token + * Tools */ -server.registerTool( - 'get_token', - { - title: 'Get Token', - description: 'Get detailed information about a specific design token by name', - inputSchema: { - tokenName: z.string().describe('The name of the design token (e.g., "color-primary", "spacing-md")'), + +const tools = [ + new GetTokenTool(), +] + +tools.forEach((tool) => { + server.registerTool( + tool.metadata.name, + { + title: tool.metadata.title, + description: tool.metadata.description, + inputSchema: tool.inputSchema, }, - }, - async ({ tokenName }) => { - const token = designTokens.find((t) => t.name === tokenName); + async (args: any) => { + const content = await tool.handler(args) - if (!token) { return { content: [ { type: 'text', - text: `Token not found: ${tokenName}\n\nAvailable tokens: ${designTokens - .map((t) => t.name) - .join(', ')}`, + text: content, }, ], - }; - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify(token, null, 2), - }, - ], - }; - } -); + } + }, + ) +}) /** * Tool: Search Tokens diff --git a/src/tools/get-token-tool.ts b/src/tools/get-token-tool.ts new file mode 100644 index 0000000..58aca64 --- /dev/null +++ b/src/tools/get-token-tool.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' + +import Tool, { type ToolInputSchema } from './tool.js' +import { designTokens } from '../optics-data.js' + +class GetTokenTool extends Tool { + name = 'get_token' + title = 'Get Token' + description = 'Get detailed information about a specific design token by name' + + inputSchema = { + tokenName: z + .string() + .describe('The name of the design token (e.g., "color-primary", "spacing-md")'), + } + + async handler(args: ToolInputSchema): Promise { + const { tokenName } = args + + const token = designTokens.find((t) => t.name === tokenName) + + if (!token) { + return `Token not found: ${tokenName}\n\nAvailable tokens: ${designTokens.map((t) => t.name).join(', ')}` + } + + return JSON.stringify(token, null, 2) + } +} + +export default GetTokenTool diff --git a/src/tools/tool.ts b/src/tools/tool.ts new file mode 100644 index 0000000..c93af70 --- /dev/null +++ b/src/tools/tool.ts @@ -0,0 +1,46 @@ +interface ToolMetadata { + name: string + title: string + description: string + uri?: string + mimeType?: string + role?: string +} + +interface ToolInputSchema { + [key: string]: any +} + +class Tool { + name: string = ''; + title: string = ''; + description: string = ''; + uri?: string; + mimeType?: string; + role?: string; + + /** + * Example: + * { tokenName: z.string().describe('The name of the design token (e.g., "color-primary", "spacing-md")') } + */ + inputSchema: ToolInputSchema = {}; + + get metadata(): ToolMetadata { + return { + name: this.name, + title: this.title, + description: this.description, + ...(this.uri !== undefined && { uri: this.uri }), + ...(this.mimeType !== undefined && { mimeType: this.mimeType }), + ...(this.role !== undefined && { role: this.role }), + } + } + + async handler(args: any): Promise { + throw new Error('Subclass should override') + } +} + +export default Tool + +export { ToolMetadata, ToolInputSchema } From 2af16685b6e8ae12b61dbea1f94eca72471ad7e0 Mon Sep 17 00:00:00 2001 From: Jeremy Walton Date: Thu, 5 Feb 2026 14:28:17 -0500 Subject: [PATCH 06/28] Extract token usage stats (#20) --- src/index.ts | 34 ++++++++----------------- src/optics-data.ts | 17 ------------- src/test.ts | 10 ++++---- src/tools/get-token-usage-stats-tool.ts | 34 +++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 46 deletions(-) create mode 100644 src/tools/get-token-usage-stats-tool.ts diff --git a/src/index.ts b/src/index.ts index fcf20b1..0e00d67 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,6 @@ import { designTokens, components, documentation, - getTokenUsageStats, getComponentTokenDependencies, } from './optics-data.js'; import { generateTheme } from './tools/theme-generator.js'; @@ -46,6 +45,7 @@ import * as useRecipePrompt from './prompts/use-recipe.js'; // Tools import GetTokenTool from './tools/get-token-tool.js'; +import GetTokenUsageStatsTool from './tools/get-token-usage-stats-tool.js'; /** * Create and configure the MCP server @@ -191,8 +191,17 @@ prompts.forEach((prompt) => { * Tools */ +// get_token ✅ +// get_token_usage_stats ✅ +// search_tokens +// list_components +// get_component_info +// get_component_tokens +// search_documentation + const tools = [ new GetTokenTool(), + new GetTokenUsageStatsTool(), ] tools.forEach((tool) => { @@ -256,29 +265,6 @@ server.registerTool( } ); -/** - * Tool: Get Token Usage Stats - */ -server.registerTool( - 'get_token_usage_stats', - { - title: 'Get Token Usage Stats', - description: 'Get statistics about design token usage across the system', - inputSchema: {}, - }, - async () => { - const stats = getTokenUsageStats(); - return { - content: [ - { - type: 'text', - text: JSON.stringify(stats, null, 2), - }, - ], - }; - } -); - /** * Tool: Get Component Info */ diff --git a/src/optics-data.ts b/src/optics-data.ts index 619b353..a14948b 100644 --- a/src/optics-data.ts +++ b/src/optics-data.ts @@ -1575,23 +1575,6 @@ export const documentation: Documentation[] = [ } ]; -/** - * Get token usage statistics - */ -export function getTokenUsageStats() { - const categoryCount: Record = {}; - - designTokens.forEach(token => { - categoryCount[token.category] = (categoryCount[token.category] || 0) + 1; - }); - - return { - totalTokens: designTokens.length, - categories: categoryCount, - tokens: designTokens - }; -} - /** * Get component token dependencies */ diff --git a/src/test.ts b/src/test.ts index a84c583..9b0381b 100644 --- a/src/test.ts +++ b/src/test.ts @@ -4,7 +4,7 @@ * Simple test script to verify the Optics MCP server functionality */ -import { designTokens, components, getTokenUsageStats, getComponentTokenDependencies } from './optics-data.js'; +import { designTokens, components, getComponentTokenDependencies } from './optics-data.js'; console.log('🧪 Testing Optics MCP Server...\n'); @@ -21,10 +21,10 @@ console.log(` Total components: ${components.length}`); console.log(` Components: ${components.map(c => c.name).join(', ')}\n`); // Test 3: Token Usage Stats -console.log('✓ Test 3: Token Usage Statistics'); -const stats = getTokenUsageStats(); -console.log(` Total tokens: ${stats.totalTokens}`); -console.log(` Categories:`, stats.categories, '\n'); +// console.log('✓ Test 3: Token Usage Statistics'); +// const stats = getTokenUsageStats(); +// console.log(` Total tokens: ${stats.totalTokens}`); +// console.log(` Categories:`, stats.categories, '\n'); // Test 4: Component Token Dependencies console.log('✓ Test 4: Component Token Dependencies'); diff --git a/src/tools/get-token-usage-stats-tool.ts b/src/tools/get-token-usage-stats-tool.ts new file mode 100644 index 0000000..a7d83a9 --- /dev/null +++ b/src/tools/get-token-usage-stats-tool.ts @@ -0,0 +1,34 @@ +import { z } from 'zod' + +import Tool, { type ToolInputSchema } from './tool.js' +import { designTokens } from '../optics-data.js' + +class GetTokenUsageStatsTool extends Tool { + name = 'get_token_usage_stats' + title = 'Get Token Usage Stats' + description = 'Get statistics about design token usage across the system' + + inputSchema = {} + + async handler(args: ToolInputSchema): Promise { + const stats = this.getTokenUsageStats() + + return JSON.stringify(stats, null, 2) + } + + private getTokenUsageStats() { + const categoryCount: Record = {}; + + designTokens.forEach(token => { + categoryCount[token.category] = (categoryCount[token.category] || 0) + 1; + }); + + return { + totalTokens: designTokens.length, + categories: categoryCount, + tokens: designTokens + } + } +} + +export default GetTokenUsageStatsTool From 50cd90b77b9ad28d71b3f1ea8b78dc33d0ea52f1 Mon Sep 17 00:00:00 2001 From: Dominic MacAulay Date: Thu, 5 Feb 2026 14:30:53 -0500 Subject: [PATCH 07/28] Extract Search Tokens Tool (#21) # Conflicts: # src/index.ts --- src/index.ts | 42 +++------------------------------ src/tools/search-tokens-tool.ts | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 39 deletions(-) create mode 100644 src/tools/search-tokens-tool.ts diff --git a/src/index.ts b/src/index.ts index 0e00d67..7ab45f4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,6 +46,7 @@ import * as useRecipePrompt from './prompts/use-recipe.js'; // Tools import GetTokenTool from './tools/get-token-tool.js'; import GetTokenUsageStatsTool from './tools/get-token-usage-stats-tool.js'; +import SearchTokensTool from './tools/search-tokens-tool.js'; /** * Create and configure the MCP server @@ -193,7 +194,7 @@ prompts.forEach((prompt) => { // get_token ✅ // get_token_usage_stats ✅ -// search_tokens +// search_tokens ✅ // list_components // get_component_info // get_component_tokens @@ -202,6 +203,7 @@ prompts.forEach((prompt) => { const tools = [ new GetTokenTool(), new GetTokenUsageStatsTool(), + new SearchTokensTool(), ] tools.forEach((tool) => { @@ -227,44 +229,6 @@ tools.forEach((tool) => { ) }) -/** - * Tool: Search Tokens - */ -server.registerTool( - 'search_tokens', - { - title: 'Search Tokens', - description: 'Search for design tokens by category or name pattern', - inputSchema: { - category: z.string().optional().describe('Filter by category (color, spacing, typography, border, shadow)'), - namePattern: z.string().optional().describe('Search pattern for token names (case-insensitive)'), - }, - }, - async ({ category, namePattern }) => { - let filtered = designTokens; - - if (category) { - filtered = filtered.filter((t) => t.category === category); - } - - if (namePattern) { - const pattern = namePattern.toLowerCase(); - filtered = filtered.filter((t) => - t.name.toLowerCase().includes(pattern) - ); - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify(filtered, null, 2), - }, - ], - }; - } -); - /** * Tool: Get Component Info */ diff --git a/src/tools/search-tokens-tool.ts b/src/tools/search-tokens-tool.ts new file mode 100644 index 0000000..ec35b9e --- /dev/null +++ b/src/tools/search-tokens-tool.ts @@ -0,0 +1,41 @@ +import { z } from 'zod' + +import Tool, { type ToolInputSchema } from './tool.js' +import { designTokens } from '../optics-data.js' + +class SearchTokensTool extends Tool { + name = 'search_tokens' + title = 'Search Tokens' + description = 'Search for design tokens by category or name pattern' + + inputSchema = { + category: z + .string() + .optional() + .describe('Filter by category (color, spacing, typography, border, shadow)'), + namePattern: z + .string() + .optional() + .describe('Search pattern for token names (case-insensitive)') + } + + async handler(args: ToolInputSchema): Promise { + const { category, namePattern } = args + let filtered = designTokens; + + if (category) { + filtered = filtered.filter((t) => t.category === category); + } + + if (namePattern) { + const pattern = namePattern.toLowerCase(); + filtered = filtered.filter((t) => + t.name.toLowerCase().includes(pattern) + ); + } + + return JSON.stringify(filtered, null, 2) + } +} + +export default SearchTokensTool From c8db4860987b076bb98c8c1047c100191c602f58 Mon Sep 17 00:00:00 2001 From: Jeremy Walton Date: Thu, 5 Feb 2026 14:38:48 -0500 Subject: [PATCH 08/28] Extract list components (#22) --- src/index.ts | 32 +++---------------------------- src/tools/list-components-tool.ts | 24 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 29 deletions(-) create mode 100644 src/tools/list-components-tool.ts diff --git a/src/index.ts b/src/index.ts index 7ab45f4..2824241 100644 --- a/src/index.ts +++ b/src/index.ts @@ -47,6 +47,7 @@ import * as useRecipePrompt from './prompts/use-recipe.js'; import GetTokenTool from './tools/get-token-tool.js'; import GetTokenUsageStatsTool from './tools/get-token-usage-stats-tool.js'; import SearchTokensTool from './tools/search-tokens-tool.js'; +import ListComponentsTool from './tools/list-components-tool.js' /** * Create and configure the MCP server @@ -195,7 +196,7 @@ prompts.forEach((prompt) => { // get_token ✅ // get_token_usage_stats ✅ // search_tokens ✅ -// list_components +// list_components ✅ // get_component_info // get_component_tokens // search_documentation @@ -204,6 +205,7 @@ const tools = [ new GetTokenTool(), new GetTokenUsageStatsTool(), new SearchTokensTool(), + new ListComponentsTool(), ] tools.forEach((tool) => { @@ -270,34 +272,6 @@ server.registerTool( } ); -/** - * Tool: List Components - */ -server.registerTool( - 'list_components', - { - title: 'List Components', - description: 'List all available components in the design system', - inputSchema: {}, - }, - async () => { - const componentList = components.map((c) => ({ - name: c.name, - description: c.description, - tokenCount: c.tokens.length, - })); - - return { - content: [ - { - type: 'text', - text: JSON.stringify(componentList, null, 2), - }, - ], - }; - } -); - /** * Tool: Get Component Tokens */ diff --git a/src/tools/list-components-tool.ts b/src/tools/list-components-tool.ts new file mode 100644 index 0000000..2490992 --- /dev/null +++ b/src/tools/list-components-tool.ts @@ -0,0 +1,24 @@ +import { z } from 'zod' + +import Tool, { type ToolInputSchema } from './tool.js' +import { components } from '../optics-data.js' + +class ListComponentsTool extends Tool { + name = 'list_components' + title = 'List Components' + description = 'List all available components in the design system' + + inputSchema = {} + + async handler(args: ToolInputSchema): Promise { + const componentList = components.map((c) => ({ + name: c.name, + description: c.description, + tokenCount: c.tokens.length, + })); + + return JSON.stringify(componentList, null, 2) + } +} + +export default ListComponentsTool From 41601ab4a3320c2b3540a0fdfd67683c36469beb Mon Sep 17 00:00:00 2001 From: Dominic MacAulay Date: Thu, 5 Feb 2026 15:02:58 -0500 Subject: [PATCH 09/28] Extract Get Component Info Tool (#23) * extract # Conflicts: # src/index.ts * Remove unneeded import --- src/index.ts | 46 ++-------------------------- src/tools/get-component-info-tool.ts | 35 +++++++++++++++++++++ 2 files changed, 38 insertions(+), 43 deletions(-) create mode 100644 src/tools/get-component-info-tool.ts diff --git a/src/index.ts b/src/index.ts index 2824241..687218b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,6 @@ import type { ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcon import { z } from 'zod'; import { designTokens, - components, documentation, getComponentTokenDependencies, } from './optics-data.js'; @@ -48,6 +47,7 @@ import GetTokenTool from './tools/get-token-tool.js'; import GetTokenUsageStatsTool from './tools/get-token-usage-stats-tool.js'; import SearchTokensTool from './tools/search-tokens-tool.js'; import ListComponentsTool from './tools/list-components-tool.js' +import GetComponentInfoTool from './tools/get-component-info-tool.js'; /** * Create and configure the MCP server @@ -197,7 +197,7 @@ prompts.forEach((prompt) => { // get_token_usage_stats ✅ // search_tokens ✅ // list_components ✅ -// get_component_info +// get_component_info ✅ // get_component_tokens // search_documentation @@ -206,6 +206,7 @@ const tools = [ new GetTokenUsageStatsTool(), new SearchTokensTool(), new ListComponentsTool(), + new GetComponentInfoTool(), ] tools.forEach((tool) => { @@ -231,47 +232,6 @@ tools.forEach((tool) => { ) }) -/** - * Tool: Get Component Info - */ -server.registerTool( - 'get_component_info', - { - title: 'Get Component Info', - description: 'Get detailed information about a component including its design token dependencies', - inputSchema: { - componentName: z.string().describe('The name of the component (e.g., "Button", "Card", "Input")'), - }, - }, - async ({ componentName }) => { - const component = components.find( - (c) => c.name.toLowerCase() === componentName.toLowerCase() - ); - - if (!component) { - return { - content: [ - { - type: 'text', - text: `Component not found: ${componentName}\n\nAvailable components: ${components - .map((c) => c.name) - .join(', ')}`, - }, - ], - }; - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify(component, null, 2), - }, - ], - }; - } -); - /** * Tool: Get Component Tokens */ diff --git a/src/tools/get-component-info-tool.ts b/src/tools/get-component-info-tool.ts new file mode 100644 index 0000000..f0f01da --- /dev/null +++ b/src/tools/get-component-info-tool.ts @@ -0,0 +1,35 @@ +import { z } from 'zod' + +import Tool, { type ToolInputSchema } from './tool.js' +import { components } from '../optics-data.js' + +class GetComponentInfoTool extends Tool { + name = 'get_component_info' + title = 'Get Component Info' + description = 'Get detailed information about a component including its design token dependencies' + + inputSchema = { + componentName: z + .string() + .describe('The name of the component (e.g., "Button", "Card", "Input")') + } + + async handler(args: ToolInputSchema): Promise { + const component = components.find( + (c) => c.name.toLowerCase() === args.componentName.toLowerCase() + ) + + let value = '' + + if (!component) { + const availableComponents = components.map((c) => c.name).join(', ') + value = `Component not found: ${args.componentName}\n\nAvailable components: ${availableComponents}` + } else { + value = JSON.stringify(component, null, 2) + } + + return value + } +} + +export default GetComponentInfoTool From 7d91eaf152d25fe6461a9ea03bcb4340335abc1f Mon Sep 17 00:00:00 2001 From: Dominic MacAulay Date: Thu, 5 Feb 2026 15:18:24 -0500 Subject: [PATCH 10/28] Extract Get Component Tokens Tool (#24) * Extract Get Component Tokens Tool * Address comment --- src/index.ts | 47 ++--------------------- src/optics-data.ts | 24 ------------ src/test.ts | 14 +++---- src/tools/get-component-tokens-tool.ts | 52 ++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 74 deletions(-) create mode 100644 src/tools/get-component-tokens-tool.ts diff --git a/src/index.ts b/src/index.ts index 687218b..58496c9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,11 +11,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import type { ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types" import { z } from 'zod'; -import { - designTokens, - documentation, - getComponentTokenDependencies, -} from './optics-data.js'; +import { designTokens, documentation } from './optics-data.js'; import { generateTheme } from './tools/theme-generator.js'; import { validateTokenUsage, formatValidationReport } from './tools/validate.js'; import { replaceHardCodedValues, formatReplacementSuggestions } from './tools/replace.js'; @@ -48,6 +44,7 @@ import GetTokenUsageStatsTool from './tools/get-token-usage-stats-tool.js'; import SearchTokensTool from './tools/search-tokens-tool.js'; import ListComponentsTool from './tools/list-components-tool.js' import GetComponentInfoTool from './tools/get-component-info-tool.js'; +import GetComponentTokensTool from './tools/get-component-tokens-tool.js'; /** * Create and configure the MCP server @@ -198,7 +195,7 @@ prompts.forEach((prompt) => { // search_tokens ✅ // list_components ✅ // get_component_info ✅ -// get_component_tokens +// get_component_tokens ✅ // search_documentation const tools = [ @@ -207,6 +204,7 @@ const tools = [ new SearchTokensTool(), new ListComponentsTool(), new GetComponentInfoTool(), + new GetComponentTokensTool(), ] tools.forEach((tool) => { @@ -232,43 +230,6 @@ tools.forEach((tool) => { ) }) -/** - * Tool: Get Component Tokens - */ -server.registerTool( - 'get_component_tokens', - { - title: 'Get Component Tokens', - description: 'Get all design tokens used by a specific component', - inputSchema: { - componentName: z.string().describe('The name of the component'), - }, - }, - async ({ componentName }) => { - const deps = getComponentTokenDependencies(componentName); - - if (!deps) { - return { - content: [ - { - type: 'text', - text: `Component not found: ${componentName}`, - }, - ], - }; - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify(deps, null, 2), - }, - ], - }; - } -); - /** * Tool: Search Documentation */ diff --git a/src/optics-data.ts b/src/optics-data.ts index a14948b..95ff948 100644 --- a/src/optics-data.ts +++ b/src/optics-data.ts @@ -1574,27 +1574,3 @@ export const documentation: Documentation[] = [ tokens: [] } ]; - -/** - * Get component token dependencies - */ -export function getComponentTokenDependencies(componentName: string) { - const component = components.find(c => - c.name.toLowerCase() === componentName.toLowerCase() - ); - - if (!component) { - return null; - } - - const tokenDetails = component.tokens.map(tokenName => - designTokens.find(t => t.name === tokenName) - ).filter((token): token is DesignToken => token !== undefined); - - return { - component: component.name, - description: component.description, - tokenCount: component.tokens.length, - tokens: tokenDetails - }; -} diff --git a/src/test.ts b/src/test.ts index 9b0381b..f4b6237 100644 --- a/src/test.ts +++ b/src/test.ts @@ -4,7 +4,7 @@ * Simple test script to verify the Optics MCP server functionality */ -import { designTokens, components, getComponentTokenDependencies } from './optics-data.js'; +import { designTokens, components } from './optics-data.js'; console.log('🧪 Testing Optics MCP Server...\n'); @@ -27,12 +27,12 @@ console.log(` Components: ${components.map(c => c.name).join(', ')}\n`); // console.log(` Categories:`, stats.categories, '\n'); // Test 4: Component Token Dependencies -console.log('✓ Test 4: Component Token Dependencies'); -const buttonDeps = getComponentTokenDependencies('Button'); -if (buttonDeps) { - console.log(` ${buttonDeps.component}: ${buttonDeps.tokenCount} tokens`); - console.log(` First token: ${buttonDeps.tokens[0]?.name}\n`); -} +// console.log('✓ Test 4: Component Token Dependencies'); +// const buttonDeps = getComponentTokenDependencies('Button'); +// if (buttonDeps) { +// console.log(` ${buttonDeps.component}: ${buttonDeps.tokenCount} tokens`); +// console.log(` First token: ${buttonDeps.tokens[0]?.name}\n`); +// } // Test 5: Search functionality console.log('✓ Test 5: Token Search'); diff --git a/src/tools/get-component-tokens-tool.ts b/src/tools/get-component-tokens-tool.ts new file mode 100644 index 0000000..eb5f115 --- /dev/null +++ b/src/tools/get-component-tokens-tool.ts @@ -0,0 +1,52 @@ +import { z } from 'zod' + +import Tool, { type ToolInputSchema } from './tool.js' +import { components, designTokens, type DesignToken } from '../optics-data.js' + +class GetComponentTokensTool extends Tool { + name = 'get_component_tokens' + title = 'Get Component Tokens' + description = 'Get all design tokens used by a specific component' + + inputSchema = { + componentName: z + .string() + .describe('The name of the component (e.g., "Button", "Card", "Input")') + } + + async handler(args: ToolInputSchema): Promise { + const dependencies = this.getComponentTokenDependencies(args.componentName); + + let value = '' + + if (!dependencies) { + const availableComponents = components.map((c) => c.name).join(', ') + value = `Component not found: ${args.componentName}\n\nAvailable components: ${availableComponents}` + } else { + value = JSON.stringify(dependencies, null, 2) + } + + return value + } + + private getComponentTokenDependencies(componentName: string) { + const component = components.find(c => + c.name.toLowerCase() === componentName.toLowerCase() + ); + + if (!component) return null + + const tokenDetails = component.tokens.map(tokenName => + designTokens.find(t => t.name.replace(/^--op/, 'op') === tokenName.replace(/^--op/, 'op')) + ).filter((token): token is DesignToken => token !== undefined); + + return { + component: component.name, + description: component.description, + tokenCount: component.tokens.length, + tokens: tokenDetails + }; + } +} + +export default GetComponentTokensTool From 10c01d41e56107157d84cffad4e230dcd8c5ad8a Mon Sep 17 00:00:00 2001 From: Dominic MacAulay Date: Thu, 5 Feb 2026 15:22:59 -0500 Subject: [PATCH 11/28] Extract Search Documentation Tool (#25) * Extract Search Documentation Tool # Conflicts: # src/index.ts * Remove checklist --- src/index.ts | 43 ++------------------------ src/tools/search-documentation-tool.ts | 30 ++++++++++++++++++ 2 files changed, 33 insertions(+), 40 deletions(-) create mode 100644 src/tools/search-documentation-tool.ts diff --git a/src/index.ts b/src/index.ts index 58496c9..bd9150f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,7 +11,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import type { ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types" import { z } from 'zod'; -import { designTokens, documentation } from './optics-data.js'; +import { designTokens } from './optics-data.js'; import { generateTheme } from './tools/theme-generator.js'; import { validateTokenUsage, formatValidationReport } from './tools/validate.js'; import { replaceHardCodedValues, formatReplacementSuggestions } from './tools/replace.js'; @@ -45,6 +45,7 @@ import SearchTokensTool from './tools/search-tokens-tool.js'; import ListComponentsTool from './tools/list-components-tool.js' import GetComponentInfoTool from './tools/get-component-info-tool.js'; import GetComponentTokensTool from './tools/get-component-tokens-tool.js'; +import SearchDocumentationTool from './tools/search-documentation-tool.js'; /** * Create and configure the MCP server @@ -190,14 +191,6 @@ prompts.forEach((prompt) => { * Tools */ -// get_token ✅ -// get_token_usage_stats ✅ -// search_tokens ✅ -// list_components ✅ -// get_component_info ✅ -// get_component_tokens ✅ -// search_documentation - const tools = [ new GetTokenTool(), new GetTokenUsageStatsTool(), @@ -205,6 +198,7 @@ const tools = [ new ListComponentsTool(), new GetComponentInfoTool(), new GetComponentTokensTool(), + new SearchDocumentationTool() ] tools.forEach((tool) => { @@ -230,37 +224,6 @@ tools.forEach((tool) => { ) }) -/** - * Tool: Search Documentation - */ -server.registerTool( - 'search_documentation', - { - title: 'Search Documentation', - description: 'Search through Optics documentation', - inputSchema: { - query: z.string().describe('Search query for documentation content'), - }, - }, - async ({ query }) => { - const results = documentation.filter( - (doc) => - doc.title.toLowerCase().includes(query.toLowerCase()) || - doc.content.toLowerCase().includes(query.toLowerCase()) || - doc.section.toLowerCase().includes(query.toLowerCase()) - ); - - return { - content: [ - { - type: 'text', - text: JSON.stringify(results, null, 2), - }, - ], - }; - } -); - /** * Tool: Generate Theme */ diff --git a/src/tools/search-documentation-tool.ts b/src/tools/search-documentation-tool.ts new file mode 100644 index 0000000..25f2366 --- /dev/null +++ b/src/tools/search-documentation-tool.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' + +import Tool, { type ToolInputSchema } from './tool.js' +import { documentation } from '../optics-data.js' + +class SearchDocumentationTool extends Tool { + name = 'search_documentation' + title = 'Search Documentation' + description = 'Search through Optics documentation' + + inputSchema = { + query: z + .string() + .describe('Search query for documentation content') + } + + async handler(args: ToolInputSchema): Promise { + const { query } = args + const results = documentation.filter( + (doc) => + doc.title.toLowerCase().includes(query.toLowerCase()) || + doc.content.toLowerCase().includes(query.toLowerCase()) || + doc.section.toLowerCase().includes(query.toLowerCase()) + ) + + return JSON.stringify(results, null, 2) + } +} + +export default SearchDocumentationTool From d83afa4ad9cedc8b82dc5a7b39b15aa9d9db89da Mon Sep 17 00:00:00 2001 From: Jeremy Walton Date: Thu, 5 Feb 2026 15:40:15 -0500 Subject: [PATCH 12/28] Extract the replace hard coded values tool (#26) * extract it * Remove old tool --- src/index.ts | 32 +----- src/tools/replace-hard-coded-values-tool.ts | 111 ++++++++++++++++++++ src/tools/replace.ts | 101 ------------------ 3 files changed, 115 insertions(+), 129 deletions(-) create mode 100644 src/tools/replace-hard-coded-values-tool.ts delete mode 100644 src/tools/replace.ts diff --git a/src/index.ts b/src/index.ts index bd9150f..390ee64 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,7 @@ import { z } from 'zod'; import { designTokens } from './optics-data.js'; import { generateTheme } from './tools/theme-generator.js'; import { validateTokenUsage, formatValidationReport } from './tools/validate.js'; -import { replaceHardCodedValues, formatReplacementSuggestions } from './tools/replace.js'; + import { checkTokenContrast, formatContrastResult } from './tools/accessibility.js'; import { suggestTokenMigration, formatMigrationSuggestions } from './tools/migration.js'; import { generateComponentScaffold, formatScaffoldOutput } from './tools/scaffold.js'; @@ -46,6 +46,7 @@ import ListComponentsTool from './tools/list-components-tool.js' import GetComponentInfoTool from './tools/get-component-info-tool.js'; import GetComponentTokensTool from './tools/get-component-tokens-tool.js'; import SearchDocumentationTool from './tools/search-documentation-tool.js'; +import ReplaceHardCodedValuesTool from './tools/replace-hard-coded-values-tool.js'; /** * Create and configure the MCP server @@ -198,7 +199,8 @@ const tools = [ new ListComponentsTool(), new GetComponentInfoTool(), new GetComponentTokensTool(), - new SearchDocumentationTool() + new SearchDocumentationTool(), + new ReplaceHardCodedValuesTool() ] tools.forEach((tool) => { @@ -284,33 +286,7 @@ server.registerTool( } ); -/** - * Tool: Replace Hard-Coded Values - */ -server.registerTool( - 'replace_hard_coded_values', - { - title: 'Replace Hard-Coded Values', - description: 'Replace hard-coded values with design tokens', - inputSchema: { - code: z.string().describe('Code containing hard-coded values'), - autofix: z.boolean().optional().describe('Whether to automatically fix the code (default: false)'), - }, - }, - async ({ code, autofix }) => { - const result = replaceHardCodedValues(code, designTokens, autofix ?? false); - const formatted = formatReplacementSuggestions(result); - return { - content: [ - { - type: 'text', - text: formatted, - }, - ], - }; - } -); /** * Tool: Check Contrast diff --git a/src/tools/replace-hard-coded-values-tool.ts b/src/tools/replace-hard-coded-values-tool.ts new file mode 100644 index 0000000..e682977 --- /dev/null +++ b/src/tools/replace-hard-coded-values-tool.ts @@ -0,0 +1,111 @@ +import { z } from 'zod' +import Tool from './tool.js' +import { designTokens, type DesignToken } from '../optics-data.js' +import { extractAllValues, findMatchingToken } from '../utils/css-parser.js' + +interface ReplacementSuggestion { + original: string + replacement: string + tokenName: string + property?: string +} + +interface ReplacementResult { + originalCode: string + fixedCode: string + replacements: ReplacementSuggestion[] + replacementCount: number +} + +class ReplaceHardCodedValuesTool extends Tool { + name = 'replace_hard_coded_values' + title = 'Replace Hard-Coded Values' + description = 'Replace hard-coded values with design tokens' + + inputSchema = { + code: z.string().describe('Code containing hard-coded values'), + autofix: z.boolean().optional().describe('Whether to automatically fix the code (default: false)'), + } + + async handler(args: any): Promise { + const { code, autofix } = args + const result = this.replaceHardCodedValues(code, designTokens, autofix ?? false) + return this.formatReplacementSuggestions(result) + } + + private replaceHardCodedValues( + code: string, + tokens: DesignToken[], + autofix: boolean = false + ): ReplacementResult { + const extractedValues = extractAllValues(code) + const replacements: ReplacementSuggestion[] = [] + let fixedCode = code + + for (const value of extractedValues) { + const matchingToken = findMatchingToken(value.value, tokens) + + if (matchingToken) { + const replacement = `var(--${matchingToken})` + replacements.push({ + original: value.value, + replacement, + tokenName: matchingToken, + property: value.property, + }) + + if (autofix) { + fixedCode = fixedCode.replace( + new RegExp(this.escapeRegex(value.value), 'g'), + replacement + ) + } + } + } + + return { + originalCode: code, + fixedCode: autofix ? fixedCode : code, + replacements, + replacementCount: replacements.length, + } + } + + private escapeRegex(str: string): string { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + } + + private formatReplacementSuggestions(result: ReplacementResult): string { + const lines: string[] = [ + '# Token Replacement Suggestions', + '', + `**Replacements Found**: ${result.replacementCount}`, + '', + ] + + if (result.replacements.length > 0) { + lines.push('## Suggested Replacements') + lines.push('') + + for (const rep of result.replacements) { + lines.push(`- Replace \`${rep.original}\` with \`${rep.replacement}\``) + lines.push(` Token: ${rep.tokenName}`) + if (rep.property) lines.push(` Property: ${rep.property}`) + lines.push('') + } + + if (result.fixedCode !== result.originalCode) { + lines.push('## Fixed Code') + lines.push('```css') + lines.push(result.fixedCode) + lines.push('```') + } + } else { + lines.push('✓ No replacements needed!') + } + + return lines.join('\n') + } +} + +export default ReplaceHardCodedValuesTool diff --git a/src/tools/replace.ts b/src/tools/replace.ts deleted file mode 100644 index 1a57179..0000000 --- a/src/tools/replace.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Token replacement tool - * Replaces hard-coded values with design tokens - */ - -import { DesignToken } from '../optics-data.js'; -import { extractAllValues, findMatchingToken } from '../utils/css-parser.js'; - -export interface ReplacementSuggestion { - original: string; - replacement: string; - tokenName: string; - property?: string; -} - -export interface ReplacementResult { - originalCode: string; - fixedCode: string; - replacements: ReplacementSuggestion[]; - replacementCount: number; -} - -/** - * Replace hard-coded values with tokens - */ -export function replaceHardCodedValues( - code: string, - tokens: DesignToken[], - autofix: boolean = false -): ReplacementResult { - const extractedValues = extractAllValues(code); - const replacements: ReplacementSuggestion[] = []; - let fixedCode = code; - - for (const value of extractedValues) { - const matchingToken = findMatchingToken(value.value, tokens); - - if (matchingToken) { - const replacement = `var(--${matchingToken})`; - replacements.push({ - original: value.value, - replacement, - tokenName: matchingToken, - property: value.property - }); - - if (autofix) { - fixedCode = fixedCode.replace( - new RegExp(escapeRegex(value.value), 'g'), - replacement - ); - } - } - } - - return { - originalCode: code, - fixedCode: autofix ? fixedCode : code, - replacements, - replacementCount: replacements.length - }; -} - -function escapeRegex(str: string): string { - return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -} - -/** - * Format replacement suggestions - */ -export function formatReplacementSuggestions(result: ReplacementResult): string { - const lines: string[] = [ - '# Token Replacement Suggestions', - '', - `**Replacements Found**: ${result.replacementCount}`, - '' - ]; - - if (result.replacements.length > 0) { - lines.push('## Suggested Replacements'); - lines.push(''); - - for (const rep of result.replacements) { - lines.push(`- Replace \`${rep.original}\` with \`${rep.replacement}\``); - lines.push(` Token: ${rep.tokenName}`); - if (rep.property) lines.push(` Property: ${rep.property}`); - lines.push(''); - } - - if (result.fixedCode !== result.originalCode) { - lines.push('## Fixed Code'); - lines.push('```css'); - lines.push(result.fixedCode); - lines.push('```'); - } - } else { - lines.push('✓ No replacements needed!'); - } - - return lines.join('\n'); -} From 79fb28c4786db5286bc9dddf073e560c16985bd6 Mon Sep 17 00:00:00 2001 From: Dominic MacAulay Date: Thu, 5 Feb 2026 16:54:26 -0500 Subject: [PATCH 13/28] Extract remaining tools (#27) * Create read tool file method * extract the theme generator * Finish tool class extractions * Extract md files * specifiy import type * update type * removed unused parameter * removed useless variable stuff --- src/_internal/resource-path.ts | 9 +- src/index.ts | 222 +--------- src/tools/accessibility.ts | 148 ------- src/tools/check-contrast-error.md | 7 + src/tools/check-contrast-result.md | 9 + src/tools/check-contrast-tool.ts | 148 +++++++ src/tools/generate-component-scaffold-tool.ts | 216 ++++++++++ .../generate-component-scaffold-usage.md | 23 ++ .../generate-sticker-sheet-instructions.md | 22 + ...heet.ts => generate-sticker-sheet-tool.ts} | 309 +++++++------- src/tools/generate-theme-instructions.md | 63 +++ src/tools/generate-theme-output.md | 24 ++ src/tools/generate-theme-tool.ts | 286 +++++++++++++ src/tools/migration.ts | 149 ------- src/tools/replace-hard-coded-values-tool.ts | 4 +- src/tools/scaffold.ts | 208 ---------- src/tools/suggest-token-migration-header.md | 5 + src/tools/suggest-token-migration-none.md | 6 + src/tools/suggest-token-migration-tool.ts | 169 ++++++++ src/tools/theme-generator.ts | 387 ------------------ src/tools/validate-token-usage-header.md | 7 + src/tools/validate-token-usage-tool.ts | 138 +++++++ src/tools/validate-token-usage-valid.md | 7 + src/tools/validate.ts | 121 ------ 24 files changed, 1303 insertions(+), 1384 deletions(-) delete mode 100644 src/tools/accessibility.ts create mode 100644 src/tools/check-contrast-error.md create mode 100644 src/tools/check-contrast-result.md create mode 100644 src/tools/check-contrast-tool.ts create mode 100644 src/tools/generate-component-scaffold-tool.ts create mode 100644 src/tools/generate-component-scaffold-usage.md create mode 100644 src/tools/generate-sticker-sheet-instructions.md rename src/tools/{sticker-sheet.ts => generate-sticker-sheet-tool.ts} (67%) create mode 100644 src/tools/generate-theme-instructions.md create mode 100644 src/tools/generate-theme-output.md create mode 100644 src/tools/generate-theme-tool.ts delete mode 100644 src/tools/migration.ts delete mode 100644 src/tools/scaffold.ts create mode 100644 src/tools/suggest-token-migration-header.md create mode 100644 src/tools/suggest-token-migration-none.md create mode 100644 src/tools/suggest-token-migration-tool.ts delete mode 100644 src/tools/theme-generator.ts create mode 100644 src/tools/validate-token-usage-header.md create mode 100644 src/tools/validate-token-usage-tool.ts create mode 100644 src/tools/validate-token-usage-valid.md delete mode 100644 src/tools/validate.ts diff --git a/src/_internal/resource-path.ts b/src/_internal/resource-path.ts index 1393076..8cc1ca6 100644 --- a/src/_internal/resource-path.ts +++ b/src/_internal/resource-path.ts @@ -16,4 +16,11 @@ const readPromptFile = async (filename: string): Promise => { return readFileSync(filePath, 'utf-8') } -export { readResourceFile, readPromptFile } +const readToolFile = async (filename: string): Promise => { + const currentDir = dirname(fileURLToPath(import.meta.url)) + const filePath = join(currentDir, '..', 'tools', filename) + + return readFileSync(filePath, 'utf-8') +} + +export { readResourceFile, readPromptFile, readToolFile } diff --git a/src/index.ts b/src/index.ts index 390ee64..623480f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,14 +12,6 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import type { ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types" import { z } from 'zod'; import { designTokens } from './optics-data.js'; -import { generateTheme } from './tools/theme-generator.js'; -import { validateTokenUsage, formatValidationReport } from './tools/validate.js'; - -import { checkTokenContrast, formatContrastResult } from './tools/accessibility.js'; -import { suggestTokenMigration, formatMigrationSuggestions } from './tools/migration.js'; -import { generateComponentScaffold, formatScaffoldOutput } from './tools/scaffold.js'; -import { generateStickerSheet, formatStickerSheet } from './tools/sticker-sheet.js'; -import { getRecipe, searchRecipes, formatRecipe, formatRecipeList } from './tools/recipes.js'; // Resources import * as systemOverview from './resources/system-overview.js'; @@ -35,8 +27,6 @@ import * as accessibleColorComboPrompt from './prompts/accessible-color-combo.js import * as designReviewPrompt from './prompts/design-review.js'; import * as explainTokenSystemPrompt from './prompts/explain-token-system.js'; import * as getTokenReferencePrompt from './prompts/get-token-reference.js'; -import * as configureIconsPrompt from './prompts/configure-icons.js'; -import * as useRecipePrompt from './prompts/use-recipe.js'; // Tools import GetTokenTool from './tools/get-token-tool.js'; @@ -46,7 +36,13 @@ import ListComponentsTool from './tools/list-components-tool.js' import GetComponentInfoTool from './tools/get-component-info-tool.js'; import GetComponentTokensTool from './tools/get-component-tokens-tool.js'; import SearchDocumentationTool from './tools/search-documentation-tool.js'; +import GenerateThemeTool from './tools/generate-theme-tool.js'; +import ValidateTokenUsageTool from './tools/validate-token-usage-tool.js'; import ReplaceHardCodedValuesTool from './tools/replace-hard-coded-values-tool.js'; +import CheckContrastTool from './tools/check-contrast-tool.js'; +import SuggestTokenMigrationTool from './tools/suggest-token-migration-tool.js'; +import GenerateComponentScaffoldTool from './tools/generate-component-scaffold-tool.js'; +import GenerateStickerSheetTool from './tools/generate-sticker-sheet-tool.js'; /** * Create and configure the MCP server @@ -54,12 +50,6 @@ import ReplaceHardCodedValuesTool from './tools/replace-hard-coded-values-tool.j const server = new McpServer({ name: 'optics-mcp', version: '0.1.0', -}, { - capabilities: { - tools: {}, - prompts: {}, - resources: {}, - } }); /** @@ -156,9 +146,7 @@ const prompts = [ accessibleColorComboPrompt, designReviewPrompt, explainTokenSystemPrompt, - getTokenReferencePrompt, - configureIconsPrompt, - useRecipePrompt + getTokenReferencePrompt ] prompts.forEach((prompt) => { @@ -200,7 +188,13 @@ const tools = [ new GetComponentInfoTool(), new GetComponentTokensTool(), new SearchDocumentationTool(), - new ReplaceHardCodedValuesTool() + new GenerateThemeTool(), + new ValidateTokenUsageTool(), + new ReplaceHardCodedValuesTool(), + new CheckContrastTool(), + new SuggestTokenMigrationTool(), + new GenerateComponentScaffoldTool(), + new GenerateStickerSheetTool() ] tools.forEach((tool) => { @@ -226,194 +220,6 @@ tools.forEach((tool) => { ) }) -/** - * Tool: Generate Theme - */ -server.registerTool( - 'generate_theme', - { - title: 'Generate Theme', - description: 'Generate a custom Optics theme with CSS variable overrides', - inputSchema: { - brandName: z.string().describe('Name of the brand/theme (e.g., "Acme Corp")'), - primary: z.string().describe('Primary brand color (hex, e.g., "#0066CC")'), - secondary: z.string().optional().describe('Secondary color (hex, optional)'), - }, - }, - async ({ brandName, primary, secondary }) => { - const brandColors = { - primary, - secondary, - }; - - const theme = generateTheme(brandName, brandColors); - - return { - content: [ - { - type: 'text', - text: `# ${brandName} Theme Generated\n\n## CSS Variables\n\n\`\`\`css\n${theme.cssVariables}\n\`\`\`\n\n## Figma Variables\n\nSave this as \`figma-variables.json\`:\n\n\`\`\`json\n${theme.figmaVariables}\n\`\`\`\n\n## Summary\n\n- **Total tokens**: ${theme.tokens.length}\n- **Colors**: ${theme.tokens.filter(t => t.category === 'color').length}\n- **Typography**: ${theme.tokens.filter(t => t.category === 'typography').length}\n- **Spacing**: ${theme.tokens.filter(t => t.category === 'spacing').length}\n\n${theme.documentation}`, - }, - ], - }; - } -); - -/** - * Tool: Validate Token Usage - */ -server.registerTool( - 'validate_token_usage', - { - title: 'Validate Token Usage', - description: 'Validate code for hard-coded values that should use design tokens', - inputSchema: { - code: z.string().describe('CSS or component code to validate'), - }, - }, - async ({ code }) => { - const report = validateTokenUsage(code, designTokens); - const formatted = formatValidationReport(report); - - return { - content: [ - { - type: 'text', - text: formatted, - }, - ], - }; - } -); - - - -/** - * Tool: Check Contrast - */ -server.registerTool( - 'check_contrast', - { - title: 'Check Contrast', - description: 'Check WCAG contrast ratio between two color tokens', - inputSchema: { - foregroundToken: z.string().describe('Foreground color token name'), - backgroundToken: z.string().describe('Background color token name'), - }, - }, - async ({ foregroundToken, backgroundToken }) => { - const result = checkTokenContrast(foregroundToken, backgroundToken, designTokens); - const formatted = formatContrastResult(result); - - return { - content: [ - { - type: 'text', - text: formatted, - }, - ], - }; - } -); - -/** - * Tool: Suggest Token Migration - */ -server.registerTool( - 'suggest_token_migration', - { - title: 'Suggest Token Migration', - description: 'Suggest design tokens for a hard-coded value', - inputSchema: { - value: z.string().describe('Hard-coded value to find tokens for (e.g., "#0066CC", "16px")'), - category: z.string().optional().describe('Optional category filter (color, spacing, typography)'), - }, - }, - async ({ value, category }) => { - const suggestion = suggestTokenMigration(value, designTokens, category); - const formatted = formatMigrationSuggestions(suggestion); - - return { - content: [ - { - type: 'text', - text: formatted, - }, - ], - }; - } -); - -/** - * Tool: Generate Component Scaffold - */ -server.registerTool( - 'generate_component_scaffold', - { - title: 'Generate Component Scaffold', - description: 'Generate a React component scaffold with proper token usage', - inputSchema: { - componentName: z.string().describe('Name of the component (e.g., "Alert", "Card")'), - description: z.string().describe('Brief description of the component'), - tokens: z.array(z.string()).describe('List of token names the component should use'), - }, - }, - async ({ componentName, description, tokens }) => { - const scaffold = generateComponentScaffold( - componentName, - description, - tokens, - designTokens - ); - const formatted = formatScaffoldOutput(scaffold); - - return { - content: [ - { - type: 'text', - text: formatted, - }, - ], - }; - } -); - -/** - * Tool: Generate Sticker Sheet - */ -server.registerTool( - 'generate_sticker_sheet', - { - title: 'Generate Sticker Sheet', - description: 'Generate a visual style guide with color swatches and component examples', - inputSchema: { - framework: z.enum(['react', 'vue', 'svelte', 'html']).optional().describe('Target framework (default: react)'), - includeColors: z.boolean().optional().describe('Include color swatches (default: true)'), - includeTypography: z.boolean().optional().describe('Include typography specimens (default: true)'), - includeComponents: z.boolean().optional().describe('Include component examples (default: true)'), - }, - }, - async ({ framework, includeColors, includeTypography, includeComponents }) => { - const options = { - framework: framework ?? 'react', - includeColors: includeColors ?? true, - includeTypography: includeTypography ?? true, - includeComponents: includeComponents ?? true, - }; - const sheet = generateStickerSheet(designTokens, components, options); - const formatted = formatStickerSheet(sheet); - - return { - content: [ - { - type: 'text', - text: formatted, - }, - ], - }; - } -); - /** * Start the server */ diff --git a/src/tools/accessibility.ts b/src/tools/accessibility.ts deleted file mode 100644 index 5a44602..0000000 --- a/src/tools/accessibility.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Accessibility checker tool - * Validates WCAG contrast ratios for token combinations - */ - -import { DesignToken } from '../optics-data.js'; -import { checkContrast, ContrastResult } from '../utils/color.js'; - -export interface ContrastCheckResult { - foregroundToken: string; - backgroundToken: string; - foregroundValue: string; - backgroundValue: string; - contrast: ContrastResult | null; - passes: boolean; - recommendation?: string; -} - -/** - * Check contrast between two tokens - */ -export function checkTokenContrast( - foregroundToken: string, - backgroundToken: string, - tokens: DesignToken[] -): ContrastCheckResult { - const fgToken = tokens.find(t => t.name === foregroundToken); - const bgToken = tokens.find(t => t.name === backgroundToken); - - if (!fgToken || !bgToken) { - return { - foregroundToken, - backgroundToken, - foregroundValue: '', - backgroundValue: '', - contrast: null, - passes: false, - recommendation: 'Token not found' - }; - } - - const contrast = checkContrast(fgToken.value, bgToken.value); - - if (!contrast) { - return { - foregroundToken, - backgroundToken, - foregroundValue: fgToken.value, - backgroundValue: bgToken.value, - contrast: null, - passes: false, - recommendation: 'Unable to calculate contrast (non-color tokens?)' - }; - } - - const passes = contrast.wcagAA; - let recommendation = ''; - - if (!passes) { - recommendation = findBetterTokenCombination(fgToken, tokens, bgToken.value); - } - - return { - foregroundToken, - backgroundToken, - foregroundValue: fgToken.value, - backgroundValue: bgToken.value, - contrast, - passes, - recommendation - }; -} - -/** - * Find better token combination with sufficient contrast - */ -function findBetterTokenCombination( - currentToken: DesignToken, - allTokens: DesignToken[], - backgroundValue: string -): string { - const colorTokens = allTokens.filter(t => t.category === 'color'); - - for (const token of colorTokens) { - const contrast = checkContrast(token.value, backgroundValue); - if (contrast && contrast.wcagAA) { - return `Try using ${token.name} (${token.value}) for better contrast`; - } - } - - return 'No alternative tokens found with sufficient contrast'; -} - -/** - * Format contrast check result - */ -export function formatContrastResult(result: ContrastCheckResult): string { - const lines: string[] = [ - '# Contrast Check Result', - '', - `**Foreground**: ${result.foregroundToken} (\`${result.foregroundValue}\`)`, - `**Background**: ${result.backgroundToken} (\`${result.backgroundValue}\`)`, - '' - ]; - - if (result.contrast) { - lines.push(`**Contrast Ratio**: ${result.contrast.ratio}:1`); - lines.push(`**WCAG AA**: ${result.contrast.wcagAA ? '✓ Pass' : '✗ Fail'}`); - lines.push(`**WCAG AAA**: ${result.contrast.wcagAAA ? '✓ Pass' : '✗ Fail'}`); - lines.push(`**Score**: ${result.contrast.score}`); - lines.push(''); - - if (!result.passes && result.recommendation) { - lines.push('## Recommendation'); - lines.push(result.recommendation); - } - } else { - lines.push('✗ Unable to calculate contrast'); - if (result.recommendation) { - lines.push(`**Reason**: ${result.recommendation}`); - } - } - - return lines.join('\n'); -} - -/** - * Check all color token combinations for a given background - */ -export function checkAllCombinations( - backgroundToken: string, - tokens: DesignToken[] -): ContrastCheckResult[] { - const bgToken = tokens.find(t => t.name === backgroundToken); - if (!bgToken) return []; - - const colorTokens = tokens.filter(t => t.category === 'color' && t.name !== backgroundToken); - const results: ContrastCheckResult[] = []; - - for (const fgToken of colorTokens) { - results.push(checkTokenContrast(fgToken.name, backgroundToken, tokens)); - } - - return results.sort((a, b) => { - if (!a.contrast || !b.contrast) return 0; - return b.contrast.ratio - a.contrast.ratio; - }); -} diff --git a/src/tools/check-contrast-error.md b/src/tools/check-contrast-error.md new file mode 100644 index 0000000..fbaecf2 --- /dev/null +++ b/src/tools/check-contrast-error.md @@ -0,0 +1,7 @@ +# Contrast Check Result + +**Foreground**: {{foregroundToken}} (`{{foregroundValue}}`) +**Background**: {{backgroundToken}} (`{{backgroundValue}}`) + +✗ Unable to calculate contrast +**Reason**: {{reason}} diff --git a/src/tools/check-contrast-result.md b/src/tools/check-contrast-result.md new file mode 100644 index 0000000..06f3fab --- /dev/null +++ b/src/tools/check-contrast-result.md @@ -0,0 +1,9 @@ +# Contrast Check Result + +**Foreground**: {{foregroundToken}} (`{{foregroundValue}}`) +**Background**: {{backgroundToken}} (`{{backgroundValue}}`) + +**Contrast Ratio**: {{contrastRatio}}:1 +**WCAG AA**: {{wcagAA}} +**WCAG AAA**: {{wcagAAA}} +**Score**: {{score}} diff --git a/src/tools/check-contrast-tool.ts b/src/tools/check-contrast-tool.ts new file mode 100644 index 0000000..40f9db2 --- /dev/null +++ b/src/tools/check-contrast-tool.ts @@ -0,0 +1,148 @@ +/** + * Check Contrast Tool + * Validates WCAG contrast ratios for token combinations + */ + +import { z } from 'zod'; +import Tool, { type ToolInputSchema } from './tool.js'; +import { designTokens, type DesignToken } from '../optics-data.js'; +import { checkContrast, type ContrastResult } from '../utils/color.js'; +import { readToolFile } from '../_internal/resource-path.js'; + +export interface ContrastCheckResult { + foregroundToken: string; + backgroundToken: string; + foregroundValue: string; + backgroundValue: string; + contrast: ContrastResult | null; + passes: boolean; + recommendation?: string; +} + +class CheckContrastTool extends Tool { + name = 'check_contrast'; + title = 'Check Contrast'; + description = 'Check WCAG contrast ratio between two color tokens'; + + inputSchema = { + foregroundToken: z.string().describe('Foreground color token name'), + backgroundToken: z.string().describe('Background color token name'), + }; + + async handler(args: ToolInputSchema): Promise { + const { foregroundToken, backgroundToken } = args; + const result = this.checkTokenContrast(foregroundToken, backgroundToken, designTokens); + const formatted = await this.formatContrastResult(result); + + return formatted; + } + + /** + * Check contrast between two tokens + */ + private checkTokenContrast( + foregroundToken: string, + backgroundToken: string, + tokens: DesignToken[] + ): ContrastCheckResult { + const fgToken = tokens.find(t => t.name === foregroundToken); + const bgToken = tokens.find(t => t.name === backgroundToken); + + if (!fgToken || !bgToken) { + return { + foregroundToken, + backgroundToken, + foregroundValue: '', + backgroundValue: '', + contrast: null, + passes: false, + recommendation: 'Token not found' + }; + } + + const contrast = checkContrast(fgToken.value, bgToken.value); + + if (!contrast) { + return { + foregroundToken, + backgroundToken, + foregroundValue: fgToken.value, + backgroundValue: bgToken.value, + contrast: null, + passes: false, + recommendation: 'Unable to calculate contrast (non-color tokens?)' + }; + } + + const passes = contrast.wcagAA; + let recommendation = ''; + + if (!passes) { + recommendation = this.findBetterTokenCombination(fgToken, tokens, bgToken.value); + } + + return { + foregroundToken, + backgroundToken, + foregroundValue: fgToken.value, + backgroundValue: bgToken.value, + contrast, + passes, + recommendation + }; + } + + /** + * Find better token combination with sufficient contrast + */ + private findBetterTokenCombination( + currentToken: DesignToken, + allTokens: DesignToken[], + backgroundValue: string + ): string { + const colorTokens = allTokens.filter(t => t.category === 'color'); + + for (const token of colorTokens) { + const contrast = checkContrast(token.value, backgroundValue); + if (contrast && contrast.wcagAA) { + return `Try using ${token.name} (${token.value}) for better contrast`; + } + } + + return 'No alternative tokens found with sufficient contrast'; + } + + /** + * Format contrast check result + */ + private async formatContrastResult(result: ContrastCheckResult): Promise { + if (result.contrast) { + const template = await readToolFile('check-contrast-result.md'); + let output = template + .replace('{{foregroundToken}}', result.foregroundToken) + .replace('{{foregroundValue}}', result.foregroundValue) + .replace('{{backgroundToken}}', result.backgroundToken) + .replace('{{backgroundValue}}', result.backgroundValue) + .replace('{{contrastRatio}}', result.contrast.ratio.toString()) + .replace('{{wcagAA}}', result.contrast.wcagAA ? '✓ Pass' : '✗ Fail') + .replace('{{wcagAAA}}', result.contrast.wcagAAA ? '✓ Pass' : '✗ Fail') + .replace('{{score}}', result.contrast.score); + + if (!result.passes && result.recommendation) { + output += '\n\n## Recommendation\n' + result.recommendation; + } + + return output; + } else { + const template = await readToolFile('check-contrast-error.md'); + return template + .replace('{{foregroundToken}}', result.foregroundToken) + .replace('{{foregroundValue}}', result.foregroundValue) + .replace('{{backgroundToken}}', result.backgroundToken) + .replace('{{backgroundValue}}', result.backgroundValue) + .replace('{{reason}}', result.recommendation || 'Unknown error'); + } + } +} + +export default CheckContrastTool; diff --git a/src/tools/generate-component-scaffold-tool.ts b/src/tools/generate-component-scaffold-tool.ts new file mode 100644 index 0000000..2aae030 --- /dev/null +++ b/src/tools/generate-component-scaffold-tool.ts @@ -0,0 +1,216 @@ +/** + * Generate Component Scaffold Tool + * Generates component templates with proper token usage + */ + +import { z } from 'zod'; +import Tool, { type ToolInputSchema } from './tool.js'; +import { designTokens, type DesignToken } from '../optics-data.js'; +import { readToolFile } from '../_internal/resource-path.js'; + +export interface ComponentScaffold { + name: string; + typescript: string; + css: string; + usage: string; +} + +class GenerateComponentScaffoldTool extends Tool { + name = 'generate_component_scaffold'; + title = 'Generate Component Scaffold'; + description = 'Generate a React component scaffold with proper token usage'; + + inputSchema = { + componentName: z.string().describe('Name of the component (e.g., "Alert", "Card")'), + description: z.string().describe('Brief description of the component'), + tokens: z.array(z.string()).describe('List of token names the component should use'), + }; + + async handler(args: ToolInputSchema): Promise { + const { componentName, description, tokens } = args; + const scaffold = await this.generateComponentScaffold( + componentName, + description, + tokens, + designTokens + ); + const formatted = this.formatScaffoldOutput(scaffold); + + return formatted; + } + + /** + * Generate component scaffold + */ + private async generateComponentScaffold( + componentName: string, + description: string, + requiredTokens: string[], + allTokens: DesignToken[] + ): Promise { + const validTokens = requiredTokens.filter(tokenName => + allTokens.some(t => t.name === tokenName) + ); + + const typescript = this.generateTypeScriptComponent(componentName, description, validTokens); + const css = this.generateCSSModule(componentName, validTokens, allTokens); + const usage = await this.generateUsageExample(componentName); + + return { + name: componentName, + typescript, + css, + usage + }; + } + + /** + * Generate TypeScript component + */ + private generateTypeScriptComponent( + name: string, + description: string, + tokens: string[] + ): string { + const lines: string[] = [ + `/**`, + ` * ${name} Component`, + ` * ${description}`, + ` * `, + ` * Design tokens used:`, + ...tokens.map(t => ` * - ${t}`), + ` */`, + ``, + `import React from 'react';`, + `import styles from './${name}.module.css';`, + ``, + `export interface ${name}Props {`, + ` children: React.ReactNode;`, + ` className?: string;`, + `}`, + ``, + `export const ${name}: React.FC<${name}Props> = ({ children, className }) => {`, + ` return (`, + `
`, + ` {children}`, + `
`, + ` );`, + `};` + ]; + + return lines.join('\n'); + } + + /** + * Generate CSS module + */ + private generateCSSModule( + name: string, + tokenNames: string[], + allTokens: DesignToken[] + ): string { + const lines: string[] = [ + `/**`, + ` * ${name} Component Styles`, + ` * Uses Optics design tokens for consistent styling`, + ` */`, + ``, + `.${name.toLowerCase()} {` + ]; + + // Group tokens by category + const colorTokens = tokenNames.filter(t => allTokens.find(token => token.name === t && token.category === 'color')); + const spacingTokens = tokenNames.filter(t => allTokens.find(token => token.name === t && token.category === 'spacing')); + const typographyTokens = tokenNames.filter(t => allTokens.find(token => token.name === t && token.category === 'typography')); + const borderTokens = tokenNames.filter(t => allTokens.find(token => token.name === t && token.category === 'border')); + const shadowTokens = tokenNames.filter(t => allTokens.find(token => token.name === t && token.category === 'shadow')); + + // Add color properties + if (colorTokens.length > 0) { + lines.push(` /* Colors */`); + if (colorTokens.some(t => t.includes('background'))) { + const bgToken = colorTokens.find(t => t.includes('background')); + lines.push(` background-color: var(--${bgToken});`); + } + if (colorTokens.some(t => t.includes('text') || t.includes('color-primary'))) { + const textToken = colorTokens.find(t => t.includes('text')) || colorTokens[0]; + lines.push(` color: var(--${textToken});`); + } + } + + // Add spacing + if (spacingTokens.length > 0) { + lines.push(` /* Spacing */`); + const paddingToken = spacingTokens[0]; + lines.push(` padding: var(--${paddingToken});`); + } + + // Add typography + if (typographyTokens.length > 0) { + lines.push(` /* Typography */`); + typographyTokens.forEach(token => { + if (token.includes('font-size')) { + lines.push(` font-size: var(--${token});`); + } else if (token.includes('font-weight')) { + lines.push(` font-weight: var(--${token});`); + } else if (token.includes('line-height')) { + lines.push(` line-height: var(--${token});`); + } else if (token.includes('font-family')) { + lines.push(` font-family: var(--${token});`); + } + }); + } + + // Add borders + if (borderTokens.length > 0) { + lines.push(` /* Borders */`); + lines.push(` border-radius: var(--${borderTokens[0]});`); + } + + // Add shadows + if (shadowTokens.length > 0) { + lines.push(` /* Elevation */`); + lines.push(` box-shadow: var(--${shadowTokens[0]});`); + } + + lines.push(`}`); + lines.push(``); + + return lines.join('\n'); + } + + /** + * Generate usage example + */ + private async generateUsageExample(name: string): Promise { + const template = await readToolFile('generate-component-scaffold-usage.md'); + + return template.replace(/{{componentName}}/g, name); + } + + /** + * Format scaffold output + */ + private formatScaffoldOutput(scaffold: ComponentScaffold): string { + const lines: string[] = [ + `# ${scaffold.name} Component Scaffold`, + ``, + `## TypeScript Component`, + `\`\`\`typescript`, + scaffold.typescript, + `\`\`\``, + ``, + `## CSS Module`, + `\`\`\`css`, + scaffold.css, + `\`\`\``, + ``, + `## Usage`, + scaffold.usage + ]; + + return lines.join('\n'); + } +} + +export default GenerateComponentScaffoldTool; diff --git a/src/tools/generate-component-scaffold-usage.md b/src/tools/generate-component-scaffold-usage.md new file mode 100644 index 0000000..8c8abec --- /dev/null +++ b/src/tools/generate-component-scaffold-usage.md @@ -0,0 +1,23 @@ +# {{componentName}} Usage + +## Import + +```typescript +import { {{componentName}} } from './components/{{componentName}}'; +``` + +## Basic Usage + +```tsx +<{{componentName}}> + Your content here + +``` + +## With Custom ClassName + +```tsx +<{{componentName}} className="custom-class"> + Your content here + +``` diff --git a/src/tools/generate-sticker-sheet-instructions.md b/src/tools/generate-sticker-sheet-instructions.md new file mode 100644 index 0000000..f3dfd40 --- /dev/null +++ b/src/tools/generate-sticker-sheet-instructions.md @@ -0,0 +1,22 @@ +# Optics Sticker Sheet - {{framework}} + +This sticker sheet provides a visual reference for all Optics design tokens and components. + +## Usage + +1. Copy the component code below into your {{framework}} project +2. Copy the CSS styles into your stylesheet +3. Import and use the components in your app +4. Replace placeholders with actual component implementations + +## Files Generated + +- **Component Code**: Ready-to-use {{framework}} components +- **Styles**: CSS using Optics design tokens +- **Examples**: Visual specimens of colors, typography, and components + +## Next Steps + +- Customize the examples to match your specific components +- Add interactive states (hover, focus, disabled) +- Include additional token categories as needed diff --git a/src/tools/sticker-sheet.ts b/src/tools/generate-sticker-sheet-tool.ts similarity index 67% rename from src/tools/sticker-sheet.ts rename to src/tools/generate-sticker-sheet-tool.ts index 7f1e3c5..723b67c 100644 --- a/src/tools/sticker-sheet.ts +++ b/src/tools/generate-sticker-sheet-tool.ts @@ -1,9 +1,12 @@ /** - * Sticker sheet generator + * Generate Sticker Sheet Tool * Generates visual style guide with color swatches and component examples */ -import { DesignToken, Component } from '../optics-data.js'; +import { z } from 'zod'; +import Tool, { type ToolInputSchema } from './tool.js'; +import { designTokens, components, type DesignToken, type Component } from '../optics-data.js'; +import { readToolFile } from '../_internal/resource-path.js'; export type FrameworkType = 'react' | 'vue' | 'svelte' | 'html'; @@ -11,7 +14,6 @@ export interface StickerSheetOptions { framework?: FrameworkType; includeColors?: boolean; includeTypography?: boolean; - includeSpacing?: boolean; includeComponents?: boolean; } @@ -22,32 +24,58 @@ export interface StickerSheet { instructions: string; } -/** - * Generate color swatch component - */ -function generateColorSwatches(tokens: DesignToken[], framework: FrameworkType): string { - const colors = tokens.filter(t => t.category === 'color'); - - const swatchesData = colors.map(token => ({ - name: token.name, - value: token.value, - hsl: token.name.startsWith('color-') ? `var(--op-${token.name.replace('color-', '')}-h) var(--op-${token.name.replace('color-', '')}-s) var(--op-${token.name.replace('color-', '')}-l)` : token.value - })); - - switch (framework) { - case 'react': - return ` +class GenerateStickerSheetTool extends Tool { + name = 'generate_sticker_sheet'; + title = 'Generate Sticker Sheet'; + description = 'Generate a visual style guide with color swatches and component examples'; + + inputSchema = { + framework: z.enum(['react', 'vue', 'svelte', 'html']).optional().describe('Target framework (default: react)'), + includeColors: z.boolean().optional().describe('Include color swatches (default: true)'), + includeTypography: z.boolean().optional().describe('Include typography specimens (default: true)'), + includeComponents: z.boolean().optional().describe('Include component examples (default: true)'), + }; + + async handler(args: ToolInputSchema): Promise { + const { framework, includeColors, includeTypography, includeComponents } = args; + const options = { + framework: framework ?? 'react', + includeColors: includeColors ?? true, + includeTypography: includeTypography ?? true, + includeComponents: includeComponents ?? true, + }; + const sheet = await this.generateStickerSheet(designTokens, components, options); + const formatted = this.formatStickerSheet(sheet); + + return formatted; + } + + /** + * Generate color swatch component + */ + private generateColorSwatches(tokens: DesignToken[], framework: FrameworkType): string { + const colors = tokens.filter(t => t.category === 'color'); + + const swatchesData = colors.map(token => ({ + name: token.name, + value: token.value, + hsl: token.name.startsWith('color-') ? `var(--op-${token.name.replace('color-', '')}-h) var(--op-${token.name.replace('color-', '')}-s) var(--op-${token.name.replace('color-', '')}-l)` : token.value + })); + + switch (framework) { + case 'react': + return ` export function ColorSwatches() { const colors = ${JSON.stringify(swatchesData, null, 2)}; - + return (

Color Palette

{colors.map(color => (
-
@@ -61,15 +89,15 @@ export function ColorSwatches() { ); }`; - case 'vue': - return ` + case 'vue': + return `