@lglab/compose-ui-mcp 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -114,6 +114,24 @@ Use these tools to access component documentation:
114
114
  - Only add custom styling if the user explicitly requests it
115
115
  - Use built-in variant props (e.g., \`variant="outline"\`, \`size="sm"\`) for different appearances
116
116
  - Use specific import paths: \`import { Button } from '@lglab/compose-ui/button'\`
117
+
118
+ ## Form Building
119
+
120
+ When building forms, **always use \`get_component({ slug: "form" })\`** to retrieve complete examples before writing any form code.
121
+
122
+ The Form docs include 5 integration patterns:
123
+ - Default (useState)
124
+ - useActionState
125
+ - Zod validation
126
+ - React Hook Form
127
+ - TanStack Form
128
+
129
+ Key patterns:
130
+ - \`FormRoot\` wraps the entire form
131
+ - \`FieldRoot\` wraps each field with \`FieldLabel\`, \`FieldControl\`, \`FieldDescription\`, \`FieldError\`
132
+ - \`FieldsetRoot\` groups related fields (radio groups, checkbox groups)
133
+ - Use \`FieldValidity\` for custom validation messages
134
+ - Use the \`errors\` prop on \`FormRoot\` for server-side validation
117
135
  `;
118
136
  function registerOverviewResource(server) {
119
137
  server.registerResource("overview", "compose-ui://overview", {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["inputSchema"],"sources":["../src/lib/component-data.ts","../src/resources/components.ts","../src/resources/overview.ts","../src/resources/index.ts","../src/lib/fuzzy-match.ts","../src/tools/find-components.ts","../src/lib/markdown-parser.ts","../src/tools/get-component.ts","../src/tools/index.ts","../src/server.ts","../src/index.ts"],"sourcesContent":["import { readFile, readdir } from 'node:fs/promises'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n// After build, assets are copied to dist/ alongside index.js\n// In dev, we're in src/lib/ and assets are at ../../assets/llms\n// At runtime (from dist/index.js), llms is at ./llms\nconst LLMS_DIR = resolve(__dirname, 'llms')\n\nexport interface ComponentInfo {\n name: string\n slug: string\n description: string\n documentationUri: string\n}\n\nexport function slugToName(slug: string): string {\n return slug\n .split('-')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n}\n\nfunction parseMarkdownFile(content: string, slug: string): ComponentInfo {\n const lines = content.split('\\n')\n\n // Extract title from first # heading\n let name = slugToName(slug)\n for (const line of lines) {\n if (line.startsWith('# ')) {\n name = line.slice(2).trim()\n break\n }\n }\n\n // Extract description (first non-empty line after the heading)\n let description = ''\n let foundHeading = false\n for (const line of lines) {\n if (line.startsWith('# ')) {\n foundHeading = true\n continue\n }\n if (foundHeading && line.trim() !== '') {\n description = line.trim()\n break\n }\n }\n\n return {\n name,\n slug,\n description,\n documentationUri: `compose-ui://components/${slug}`,\n }\n}\n\nexport async function getComponentContent(slug: string): Promise<string | null> {\n try {\n const filePath = resolve(LLMS_DIR, `${slug}.md`)\n return await readFile(filePath, 'utf-8')\n } catch {\n return null\n }\n}\n\nexport async function getComponentsList(): Promise<ComponentInfo[]> {\n try {\n const files = await readdir(LLMS_DIR)\n const mdFiles = files.filter((f) => f.endsWith('.md')).sort()\n\n const components: ComponentInfo[] = []\n\n for (const file of mdFiles) {\n const slug = file.replace('.md', '')\n const content = await readFile(resolve(LLMS_DIR, file), 'utf-8')\n components.push(parseMarkdownFile(content, slug))\n }\n\n return components\n } catch {\n return []\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { getComponentContent, getComponentsList } from '../lib/component-data.js'\n\nexport function registerComponentsResource(server: McpServer): void {\n server.registerResource(\n 'components',\n 'compose-ui://components',\n {\n title: 'Compose UI Components',\n description: 'List of all Compose UI components with metadata',\n mimeType: 'application/json',\n },\n async () => ({\n contents: [\n {\n uri: 'compose-ui://components',\n mimeType: 'application/json',\n text: JSON.stringify(await getComponentsList(), null, 2),\n },\n ],\n }),\n )\n}\n\nexport function registerComponentDocResource(server: McpServer): void {\n server.registerResource(\n 'component-doc',\n new ResourceTemplate('compose-ui://components/{slug}', { list: undefined }),\n {\n title: 'Component Documentation',\n description: 'Full markdown documentation for a Compose UI component',\n mimeType: 'text/markdown',\n },\n async (uri, variables) => {\n const slug = variables.slug as string\n const content = await getComponentContent(slug)\n if (!content) {\n throw new Error(`Component not found: ${slug}`)\n }\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text: content,\n },\n ],\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nconst text = `# Compose UI\n\nA React component library built on Base UI primitives with Tailwind CSS v4 styling.\n\n## Key Principles\n- Composition over configuration\n- Base UI handles accessibility and behavior\n- Tailwind CSS for styling\n- TypeScript-first\n\n## Available Tools\n\nUse these tools to access component documentation:\n\n- **find_components**: Fuzzy search all components by name\n- **get_component**: Get full documentation for a specific component (supports section filtering)\n\n## Styling Guidelines\n\n- Components are **pre-styled and ready to use** - do not add custom CSS, Tailwind classes, or inline styles\n- Only add custom styling if the user explicitly requests it\n- Use built-in variant props (e.g., \\`variant=\"outline\"\\`, \\`size=\"sm\"\\`) for different appearances\n- Use specific import paths: \\`import { Button } from '@lglab/compose-ui/button'\\`\n`\n\nexport function registerOverviewResource(server: McpServer): void {\n server.registerResource(\n 'overview',\n 'compose-ui://overview',\n {\n title: 'Compose UI Overview',\n description: 'Compose UI overview documentation',\n mimeType: 'text/plain',\n },\n async () => ({\n contents: [\n {\n uri: 'compose-ui://overview',\n mimeType: 'text/markdown',\n text,\n },\n ],\n }),\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { registerComponentDocResource, registerComponentsResource } from './components.js'\nimport { registerOverviewResource } from './overview.js'\n\nexport function registerResources(server: McpServer): void {\n registerOverviewResource(server)\n registerComponentsResource(server)\n registerComponentDocResource(server)\n}\n","export interface FuzzyMatchResult<T> {\n item: T\n score: number\n}\n\n/**\n * Calculate a fuzzy match score between a query and a target string.\n * Returns a score from 0 to 1, where 1 is a perfect match.\n */\nexport function fuzzyScore(query: string, target: string): number {\n const q = query.toLowerCase()\n const t = target.toLowerCase()\n\n // Exact match\n if (q === t) return 1.0\n\n // Target starts with query\n if (t.startsWith(q)) return 0.9\n\n // Target contains query as substring\n if (t.includes(q)) return 0.7\n\n // Check if all query characters appear in order\n let queryIndex = 0\n let matchCount = 0\n\n for (const char of t) {\n if (queryIndex < q.length && char === q[queryIndex]) {\n matchCount++\n queryIndex++\n }\n }\n\n // All query characters found in order\n if (queryIndex === q.length) {\n // Score based on how much of the query matched vs target length\n return 0.3 + (matchCount / t.length) * 0.3\n }\n\n // Partial character match - calculate Levenshtein-like similarity\n const commonChars = new Set([...q].filter((c) => t.includes(c)))\n const similarity = commonChars.size / Math.max(q.length, t.length)\n\n return similarity * 0.3\n}\n\n/**\n * Search items using fuzzy matching.\n * Returns items sorted by match score (highest first).\n */\nexport function fuzzySearch<T>(\n query: string,\n items: T[],\n getSearchableText: (item: T) => string,\n minScore = 0.1,\n): FuzzyMatchResult<T>[] {\n const results: FuzzyMatchResult<T>[] = []\n\n for (const item of items) {\n const text = getSearchableText(item)\n const score = fuzzyScore(query, text)\n\n if (score >= minScore) {\n results.push({ item, score })\n }\n }\n\n // Sort by score descending\n results.sort((a, b) => b.score - a.score)\n\n return results\n}\n\n/**\n * Find the best fuzzy match for a query.\n * Returns the best match if score is above minScore, otherwise null.\n */\nexport function findBestMatch<T>(\n query: string,\n items: T[],\n getSearchableText: (item: T) => string,\n minScore = 0.3,\n): FuzzyMatchResult<T> | null {\n const results = fuzzySearch(query, items, getSearchableText, minScore)\n return results.length > 0 ? results[0] : null\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\n\nimport { getComponentsList } from '../lib/component-data.js'\nimport { fuzzySearch } from '../lib/fuzzy-match.js'\n\nconst inputSchema = {\n query: z\n .string()\n .optional()\n .describe('Fuzzy search query. Omit to list all components.'),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(50)\n .describe('Maximum number of results to return (default: 50)'),\n}\n\ninterface FindComponentsResult {\n count: number\n components: Array<{\n slug: string\n name: string\n description: string\n }>\n}\n\nexport function registerFindComponentsTool(server: McpServer): void {\n server.registerTool(\n 'find_components',\n {\n description:\n 'Find Compose UI components. Call without query to list all, or with query for fuzzy search.',\n inputSchema,\n },\n async ({ query, limit }) => {\n const allComponents = await getComponentsList()\n\n let components: Array<{ slug: string; name: string; description: string }>\n\n if (query) {\n // Fuzzy search on both name and slug\n const results = fuzzySearch(query, allComponents, (c) => `${c.name} ${c.slug}`)\n\n components = results.slice(0, limit ?? 50).map((r) => ({\n slug: r.item.slug,\n name: r.item.name,\n description: r.item.description,\n }))\n } else {\n // Return all components up to limit\n components = allComponents.slice(0, limit ?? 50).map((c) => ({\n slug: c.slug,\n name: c.name,\n description: c.description,\n }))\n }\n\n const result: FindComponentsResult = {\n count: components.length,\n components,\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result, null, 2),\n },\n ],\n }\n },\n )\n}\n","export type SectionType =\n | 'overview'\n | 'installation'\n | 'import'\n | 'examples'\n | 'resources'\n\nexport interface ParsedSection {\n type: SectionType\n title: string\n content: string\n}\n\nconst SECTION_MAPPINGS: Record<string, SectionType> = {\n installation: 'installation',\n import: 'import',\n examples: 'examples',\n resources: 'resources',\n}\n\ninterface RawSection {\n heading: string\n content: string\n}\n\n/**\n * Parse markdown content into sections based on ## headings.\n */\nexport function parseMarkdownSections(markdown: string): ParsedSection[] {\n const lines = markdown.split('\\n')\n const sections: ParsedSection[] = []\n\n // Find title and description (overview section)\n let title = ''\n const overviewLines: string[] = []\n let currentSection: RawSection | null = null\n const rawSections: RawSection[] = []\n\n for (const line of lines) {\n // Main title\n if (line.startsWith('# ') && !title) {\n title = line.slice(2).trim()\n continue\n }\n\n // Section heading\n if (line.startsWith('## ')) {\n // Save previous section\n if (currentSection) {\n rawSections.push(currentSection)\n } else if (overviewLines.length > 0) {\n // Save overview content collected before first ## heading\n sections.push({\n type: 'overview',\n title: title || 'Overview',\n content: `# ${title}\\n\\n${overviewLines.join('\\n').trim()}`,\n })\n }\n\n currentSection = {\n heading: line.slice(3).trim(),\n content: '',\n }\n continue\n }\n\n // Content lines\n if (currentSection) {\n currentSection.content += line + '\\n'\n } else if (title) {\n // Content before first ## heading is part of overview\n overviewLines.push(line)\n }\n }\n\n // Save last section\n if (currentSection) {\n rawSections.push(currentSection)\n } else if (overviewLines.length > 0 && sections.length === 0) {\n // No sections found, treat everything as overview\n sections.push({\n type: 'overview',\n title: title || 'Overview',\n content: `# ${title}\\n\\n${overviewLines.join('\\n').trim()}`,\n })\n }\n\n // Convert raw sections to typed sections\n for (const raw of rawSections) {\n const headingLower = raw.heading.toLowerCase()\n const sectionType = SECTION_MAPPINGS[headingLower] || 'overview'\n\n sections.push({\n type: sectionType,\n title: raw.heading,\n content: `## ${raw.heading}\\n${raw.content.trim()}`,\n })\n }\n\n return sections\n}\n\n/**\n * Extract specific sections from markdown content.\n * If no sections are specified, returns the full content.\n */\nexport function extractSections(markdown: string, sectionTypes?: SectionType[]): string {\n if (!sectionTypes || sectionTypes.length === 0) {\n return markdown\n }\n\n const parsed = parseMarkdownSections(markdown)\n const filtered = parsed.filter((s) => sectionTypes.includes(s.type))\n\n if (filtered.length === 0) {\n return ''\n }\n\n // If overview is requested, include the title\n const includesOverview = sectionTypes.includes('overview')\n const overviewSection = filtered.find((s) => s.type === 'overview')\n\n if (includesOverview && overviewSection) {\n const otherSections = filtered.filter((s) => s.type !== 'overview')\n return [overviewSection.content, ...otherSections.map((s) => s.content)].join('\\n\\n')\n }\n\n return filtered.map((s) => s.content).join('\\n\\n')\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\n\nimport {\n getComponentContent,\n getComponentsList,\n slugToName,\n} from '../lib/component-data.js'\nimport { fuzzySearch } from '../lib/fuzzy-match.js'\nimport { type SectionType, extractSections } from '../lib/markdown-parser.js'\n\nconst sectionTypes = [\n 'overview',\n 'installation',\n 'import',\n 'examples',\n 'resources',\n] as const\n\nconst inputSchema = {\n slug: z.string().describe('Component slug (e.g., \"button\", \"alert-dialog\")'),\n sections: z\n .array(z.enum(sectionTypes))\n .optional()\n .describe(\n 'Filter sections: \"overview\", \"installation\", \"import\", \"examples\", \"resources\". Default: all.',\n ),\n}\n\ninterface GetComponentSuccessResult {\n slug: string\n name: string\n found: true\n content: string\n}\n\ninterface GetComponentNotFoundResult {\n slug: string\n found: false\n suggestions: Array<{ slug: string; name: string; score: number }>\n message: string\n}\n\nexport function registerGetComponentTool(server: McpServer): void {\n server.registerTool(\n 'get_component',\n {\n description:\n 'Get documentation for a Compose UI component. Supports fuzzy matching if exact slug not found.',\n inputSchema,\n },\n async ({ slug, sections }) => {\n // Try exact match first\n const content = await getComponentContent(slug)\n\n if (content) {\n // Extract requested sections or return full content\n const filteredContent = extractSections(\n content,\n sections as SectionType[] | undefined,\n )\n\n const result: GetComponentSuccessResult = {\n slug,\n name: slugToName(slug),\n found: true,\n content: filteredContent || content,\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result, null, 2),\n },\n ],\n }\n }\n\n // No exact match - try fuzzy matching\n const allComponents = await getComponentsList()\n const fuzzyResults = fuzzySearch(slug, allComponents, (c) => c.slug, 0.2)\n\n const suggestions = fuzzyResults.slice(0, 5).map((r) => ({\n slug: r.item.slug,\n name: r.item.name,\n score: Math.round(r.score * 100) / 100,\n }))\n\n const suggestionText =\n suggestions.length > 0\n ? `Did you mean '${suggestions[0].slug}'?`\n : 'No similar components found.'\n\n const result: GetComponentNotFoundResult = {\n slug,\n found: false,\n suggestions,\n message: `Component '${slug}' not found. ${suggestionText}`,\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result, null, 2),\n },\n ],\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { registerFindComponentsTool } from './find-components.js'\nimport { registerGetComponentTool } from './get-component.js'\n\nexport function registerTools(server: McpServer): void {\n registerFindComponentsTool(server)\n registerGetComponentTool(server)\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { registerResources } from './resources/index.js'\nimport { registerTools } from './tools/index.js'\n\nexport function createServer(): McpServer {\n const server = new McpServer({\n name: 'compose-ui-mcp',\n version: '0.1.0',\n })\n\n registerResources(server)\n registerTools(server)\n\n return server\n}\n","#!/usr/bin/env node\n\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\n\nimport { createServer } from './server.js'\n\nasync function main() {\n const server = createServer()\n const transport = new StdioServerTransport()\n\n await server.connect(transport)\n\n process.on('SIGINT', async () => {\n await server.close()\n process.exit(0)\n })\n}\n\nmain().catch((error) => {\n console.error('Server error:', error) // eslint-disable-line no-console\n process.exit(1)\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["inputSchema"],"sources":["../src/lib/component-data.ts","../src/resources/components.ts","../src/resources/overview.ts","../src/resources/index.ts","../src/lib/fuzzy-match.ts","../src/tools/find-components.ts","../src/lib/markdown-parser.ts","../src/tools/get-component.ts","../src/tools/index.ts","../src/server.ts","../src/index.ts"],"sourcesContent":["import { readFile, readdir } from 'node:fs/promises'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n// After build, assets are copied to dist/ alongside index.js\n// In dev, we're in src/lib/ and assets are at ../../assets/llms\n// At runtime (from dist/index.js), llms is at ./llms\nconst LLMS_DIR = resolve(__dirname, 'llms')\n\nexport interface ComponentInfo {\n name: string\n slug: string\n description: string\n documentationUri: string\n}\n\nexport function slugToName(slug: string): string {\n return slug\n .split('-')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n}\n\nfunction parseMarkdownFile(content: string, slug: string): ComponentInfo {\n const lines = content.split('\\n')\n\n // Extract title from first # heading\n let name = slugToName(slug)\n for (const line of lines) {\n if (line.startsWith('# ')) {\n name = line.slice(2).trim()\n break\n }\n }\n\n // Extract description (first non-empty line after the heading)\n let description = ''\n let foundHeading = false\n for (const line of lines) {\n if (line.startsWith('# ')) {\n foundHeading = true\n continue\n }\n if (foundHeading && line.trim() !== '') {\n description = line.trim()\n break\n }\n }\n\n return {\n name,\n slug,\n description,\n documentationUri: `compose-ui://components/${slug}`,\n }\n}\n\nexport async function getComponentContent(slug: string): Promise<string | null> {\n try {\n const filePath = resolve(LLMS_DIR, `${slug}.md`)\n return await readFile(filePath, 'utf-8')\n } catch {\n return null\n }\n}\n\nexport async function getComponentsList(): Promise<ComponentInfo[]> {\n try {\n const files = await readdir(LLMS_DIR)\n const mdFiles = files.filter((f) => f.endsWith('.md')).sort()\n\n const components: ComponentInfo[] = []\n\n for (const file of mdFiles) {\n const slug = file.replace('.md', '')\n const content = await readFile(resolve(LLMS_DIR, file), 'utf-8')\n components.push(parseMarkdownFile(content, slug))\n }\n\n return components\n } catch {\n return []\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { getComponentContent, getComponentsList } from '../lib/component-data.js'\n\nexport function registerComponentsResource(server: McpServer): void {\n server.registerResource(\n 'components',\n 'compose-ui://components',\n {\n title: 'Compose UI Components',\n description: 'List of all Compose UI components with metadata',\n mimeType: 'application/json',\n },\n async () => ({\n contents: [\n {\n uri: 'compose-ui://components',\n mimeType: 'application/json',\n text: JSON.stringify(await getComponentsList(), null, 2),\n },\n ],\n }),\n )\n}\n\nexport function registerComponentDocResource(server: McpServer): void {\n server.registerResource(\n 'component-doc',\n new ResourceTemplate('compose-ui://components/{slug}', { list: undefined }),\n {\n title: 'Component Documentation',\n description: 'Full markdown documentation for a Compose UI component',\n mimeType: 'text/markdown',\n },\n async (uri, variables) => {\n const slug = variables.slug as string\n const content = await getComponentContent(slug)\n if (!content) {\n throw new Error(`Component not found: ${slug}`)\n }\n return {\n contents: [\n {\n uri: uri.href,\n mimeType: 'text/markdown',\n text: content,\n },\n ],\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nconst text = `# Compose UI\n\nA React component library built on Base UI primitives with Tailwind CSS v4 styling.\n\n## Key Principles\n- Composition over configuration\n- Base UI handles accessibility and behavior\n- Tailwind CSS for styling\n- TypeScript-first\n\n## Available Tools\n\nUse these tools to access component documentation:\n\n- **find_components**: Fuzzy search all components by name\n- **get_component**: Get full documentation for a specific component (supports section filtering)\n\n## Styling Guidelines\n\n- Components are **pre-styled and ready to use** - do not add custom CSS, Tailwind classes, or inline styles\n- Only add custom styling if the user explicitly requests it\n- Use built-in variant props (e.g., \\`variant=\"outline\"\\`, \\`size=\"sm\"\\`) for different appearances\n- Use specific import paths: \\`import { Button } from '@lglab/compose-ui/button'\\`\n\n## Form Building\n\nWhen building forms, **always use \\`get_component({ slug: \"form\" })\\`** to retrieve complete examples before writing any form code.\n\nThe Form docs include 5 integration patterns:\n- Default (useState)\n- useActionState\n- Zod validation\n- React Hook Form\n- TanStack Form\n\nKey patterns:\n- \\`FormRoot\\` wraps the entire form\n- \\`FieldRoot\\` wraps each field with \\`FieldLabel\\`, \\`FieldControl\\`, \\`FieldDescription\\`, \\`FieldError\\`\n- \\`FieldsetRoot\\` groups related fields (radio groups, checkbox groups)\n- Use \\`FieldValidity\\` for custom validation messages\n- Use the \\`errors\\` prop on \\`FormRoot\\` for server-side validation\n`\n\nexport function registerOverviewResource(server: McpServer): void {\n server.registerResource(\n 'overview',\n 'compose-ui://overview',\n {\n title: 'Compose UI Overview',\n description: 'Compose UI overview documentation',\n mimeType: 'text/plain',\n },\n async () => ({\n contents: [\n {\n uri: 'compose-ui://overview',\n mimeType: 'text/markdown',\n text,\n },\n ],\n }),\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { registerComponentDocResource, registerComponentsResource } from './components.js'\nimport { registerOverviewResource } from './overview.js'\n\nexport function registerResources(server: McpServer): void {\n registerOverviewResource(server)\n registerComponentsResource(server)\n registerComponentDocResource(server)\n}\n","export interface FuzzyMatchResult<T> {\n item: T\n score: number\n}\n\n/**\n * Calculate a fuzzy match score between a query and a target string.\n * Returns a score from 0 to 1, where 1 is a perfect match.\n */\nexport function fuzzyScore(query: string, target: string): number {\n const q = query.toLowerCase()\n const t = target.toLowerCase()\n\n // Exact match\n if (q === t) return 1.0\n\n // Target starts with query\n if (t.startsWith(q)) return 0.9\n\n // Target contains query as substring\n if (t.includes(q)) return 0.7\n\n // Check if all query characters appear in order\n let queryIndex = 0\n let matchCount = 0\n\n for (const char of t) {\n if (queryIndex < q.length && char === q[queryIndex]) {\n matchCount++\n queryIndex++\n }\n }\n\n // All query characters found in order\n if (queryIndex === q.length) {\n // Score based on how much of the query matched vs target length\n return 0.3 + (matchCount / t.length) * 0.3\n }\n\n // Partial character match - calculate Levenshtein-like similarity\n const commonChars = new Set([...q].filter((c) => t.includes(c)))\n const similarity = commonChars.size / Math.max(q.length, t.length)\n\n return similarity * 0.3\n}\n\n/**\n * Search items using fuzzy matching.\n * Returns items sorted by match score (highest first).\n */\nexport function fuzzySearch<T>(\n query: string,\n items: T[],\n getSearchableText: (item: T) => string,\n minScore = 0.1,\n): FuzzyMatchResult<T>[] {\n const results: FuzzyMatchResult<T>[] = []\n\n for (const item of items) {\n const text = getSearchableText(item)\n const score = fuzzyScore(query, text)\n\n if (score >= minScore) {\n results.push({ item, score })\n }\n }\n\n // Sort by score descending\n results.sort((a, b) => b.score - a.score)\n\n return results\n}\n\n/**\n * Find the best fuzzy match for a query.\n * Returns the best match if score is above minScore, otherwise null.\n */\nexport function findBestMatch<T>(\n query: string,\n items: T[],\n getSearchableText: (item: T) => string,\n minScore = 0.3,\n): FuzzyMatchResult<T> | null {\n const results = fuzzySearch(query, items, getSearchableText, minScore)\n return results.length > 0 ? results[0] : null\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\n\nimport { getComponentsList } from '../lib/component-data.js'\nimport { fuzzySearch } from '../lib/fuzzy-match.js'\n\nconst inputSchema = {\n query: z\n .string()\n .optional()\n .describe('Fuzzy search query. Omit to list all components.'),\n limit: z\n .number()\n .min(1)\n .max(100)\n .default(50)\n .describe('Maximum number of results to return (default: 50)'),\n}\n\ninterface FindComponentsResult {\n count: number\n components: Array<{\n slug: string\n name: string\n description: string\n }>\n}\n\nexport function registerFindComponentsTool(server: McpServer): void {\n server.registerTool(\n 'find_components',\n {\n description:\n 'Find Compose UI components. Call without query to list all, or with query for fuzzy search.',\n inputSchema,\n },\n async ({ query, limit }) => {\n const allComponents = await getComponentsList()\n\n let components: Array<{ slug: string; name: string; description: string }>\n\n if (query) {\n // Fuzzy search on both name and slug\n const results = fuzzySearch(query, allComponents, (c) => `${c.name} ${c.slug}`)\n\n components = results.slice(0, limit ?? 50).map((r) => ({\n slug: r.item.slug,\n name: r.item.name,\n description: r.item.description,\n }))\n } else {\n // Return all components up to limit\n components = allComponents.slice(0, limit ?? 50).map((c) => ({\n slug: c.slug,\n name: c.name,\n description: c.description,\n }))\n }\n\n const result: FindComponentsResult = {\n count: components.length,\n components,\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result, null, 2),\n },\n ],\n }\n },\n )\n}\n","export type SectionType =\n | 'overview'\n | 'installation'\n | 'import'\n | 'examples'\n | 'resources'\n\nexport interface ParsedSection {\n type: SectionType\n title: string\n content: string\n}\n\nconst SECTION_MAPPINGS: Record<string, SectionType> = {\n installation: 'installation',\n import: 'import',\n examples: 'examples',\n resources: 'resources',\n}\n\ninterface RawSection {\n heading: string\n content: string\n}\n\n/**\n * Parse markdown content into sections based on ## headings.\n */\nexport function parseMarkdownSections(markdown: string): ParsedSection[] {\n const lines = markdown.split('\\n')\n const sections: ParsedSection[] = []\n\n // Find title and description (overview section)\n let title = ''\n const overviewLines: string[] = []\n let currentSection: RawSection | null = null\n const rawSections: RawSection[] = []\n\n for (const line of lines) {\n // Main title\n if (line.startsWith('# ') && !title) {\n title = line.slice(2).trim()\n continue\n }\n\n // Section heading\n if (line.startsWith('## ')) {\n // Save previous section\n if (currentSection) {\n rawSections.push(currentSection)\n } else if (overviewLines.length > 0) {\n // Save overview content collected before first ## heading\n sections.push({\n type: 'overview',\n title: title || 'Overview',\n content: `# ${title}\\n\\n${overviewLines.join('\\n').trim()}`,\n })\n }\n\n currentSection = {\n heading: line.slice(3).trim(),\n content: '',\n }\n continue\n }\n\n // Content lines\n if (currentSection) {\n currentSection.content += line + '\\n'\n } else if (title) {\n // Content before first ## heading is part of overview\n overviewLines.push(line)\n }\n }\n\n // Save last section\n if (currentSection) {\n rawSections.push(currentSection)\n } else if (overviewLines.length > 0 && sections.length === 0) {\n // No sections found, treat everything as overview\n sections.push({\n type: 'overview',\n title: title || 'Overview',\n content: `# ${title}\\n\\n${overviewLines.join('\\n').trim()}`,\n })\n }\n\n // Convert raw sections to typed sections\n for (const raw of rawSections) {\n const headingLower = raw.heading.toLowerCase()\n const sectionType = SECTION_MAPPINGS[headingLower] || 'overview'\n\n sections.push({\n type: sectionType,\n title: raw.heading,\n content: `## ${raw.heading}\\n${raw.content.trim()}`,\n })\n }\n\n return sections\n}\n\n/**\n * Extract specific sections from markdown content.\n * If no sections are specified, returns the full content.\n */\nexport function extractSections(markdown: string, sectionTypes?: SectionType[]): string {\n if (!sectionTypes || sectionTypes.length === 0) {\n return markdown\n }\n\n const parsed = parseMarkdownSections(markdown)\n const filtered = parsed.filter((s) => sectionTypes.includes(s.type))\n\n if (filtered.length === 0) {\n return ''\n }\n\n // If overview is requested, include the title\n const includesOverview = sectionTypes.includes('overview')\n const overviewSection = filtered.find((s) => s.type === 'overview')\n\n if (includesOverview && overviewSection) {\n const otherSections = filtered.filter((s) => s.type !== 'overview')\n return [overviewSection.content, ...otherSections.map((s) => s.content)].join('\\n\\n')\n }\n\n return filtered.map((s) => s.content).join('\\n\\n')\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\n\nimport {\n getComponentContent,\n getComponentsList,\n slugToName,\n} from '../lib/component-data.js'\nimport { fuzzySearch } from '../lib/fuzzy-match.js'\nimport { type SectionType, extractSections } from '../lib/markdown-parser.js'\n\nconst sectionTypes = [\n 'overview',\n 'installation',\n 'import',\n 'examples',\n 'resources',\n] as const\n\nconst inputSchema = {\n slug: z.string().describe('Component slug (e.g., \"button\", \"alert-dialog\")'),\n sections: z\n .array(z.enum(sectionTypes))\n .optional()\n .describe(\n 'Filter sections: \"overview\", \"installation\", \"import\", \"examples\", \"resources\". Default: all.',\n ),\n}\n\ninterface GetComponentSuccessResult {\n slug: string\n name: string\n found: true\n content: string\n}\n\ninterface GetComponentNotFoundResult {\n slug: string\n found: false\n suggestions: Array<{ slug: string; name: string; score: number }>\n message: string\n}\n\nexport function registerGetComponentTool(server: McpServer): void {\n server.registerTool(\n 'get_component',\n {\n description:\n 'Get documentation for a Compose UI component. Supports fuzzy matching if exact slug not found.',\n inputSchema,\n },\n async ({ slug, sections }) => {\n // Try exact match first\n const content = await getComponentContent(slug)\n\n if (content) {\n // Extract requested sections or return full content\n const filteredContent = extractSections(\n content,\n sections as SectionType[] | undefined,\n )\n\n const result: GetComponentSuccessResult = {\n slug,\n name: slugToName(slug),\n found: true,\n content: filteredContent || content,\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result, null, 2),\n },\n ],\n }\n }\n\n // No exact match - try fuzzy matching\n const allComponents = await getComponentsList()\n const fuzzyResults = fuzzySearch(slug, allComponents, (c) => c.slug, 0.2)\n\n const suggestions = fuzzyResults.slice(0, 5).map((r) => ({\n slug: r.item.slug,\n name: r.item.name,\n score: Math.round(r.score * 100) / 100,\n }))\n\n const suggestionText =\n suggestions.length > 0\n ? `Did you mean '${suggestions[0].slug}'?`\n : 'No similar components found.'\n\n const result: GetComponentNotFoundResult = {\n slug,\n found: false,\n suggestions,\n message: `Component '${slug}' not found. ${suggestionText}`,\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(result, null, 2),\n },\n ],\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { registerFindComponentsTool } from './find-components.js'\nimport { registerGetComponentTool } from './get-component.js'\n\nexport function registerTools(server: McpServer): void {\n registerFindComponentsTool(server)\n registerGetComponentTool(server)\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\n\nimport { registerResources } from './resources/index.js'\nimport { registerTools } from './tools/index.js'\n\nexport function createServer(): McpServer {\n const server = new McpServer({\n name: 'compose-ui-mcp',\n version: '0.1.0',\n })\n\n registerResources(server)\n registerTools(server)\n\n return server\n}\n","#!/usr/bin/env node\n\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\n\nimport { createServer } from './server.js'\n\nasync function main() {\n const server = createServer()\n const transport = new StdioServerTransport()\n\n await server.connect(transport)\n\n process.on('SIGINT', async () => {\n await server.close()\n process.exit(0)\n })\n}\n\nmain().catch((error) => {\n console.error('Server error:', error) // eslint-disable-line no-console\n process.exit(1)\n})\n"],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ # Fieldset
2
+
3
+ Groups related form fields together with an accessible legend.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lglab/compose-ui
9
+ ```
10
+
11
+ ## Import
12
+
13
+ ```tsx
14
+ import { FieldsetRoot, FieldsetLegend } from '@lglab/compose-ui'
15
+ ```
16
+
17
+ ## Examples
18
+
19
+ ### Default
20
+
21
+ ```tsx
22
+ import { FieldControl, FieldLabel, FieldRoot } from '@lglab/compose-ui/field'
23
+ import { FieldsetLegend, FieldsetRoot } from '@lglab/compose-ui/fieldset'
24
+
25
+ export default function DefaultExample() {
26
+ return (
27
+ <FieldsetRoot>
28
+ <FieldsetLegend>Personal Information</FieldsetLegend>
29
+ <FieldRoot name='firstName'>
30
+ <FieldLabel>First name</FieldLabel>
31
+ <FieldControl placeholder='John' />
32
+ </FieldRoot>
33
+ <FieldRoot name='lastName'>
34
+ <FieldLabel>Last name</FieldLabel>
35
+ <FieldControl placeholder='Doe' />
36
+ </FieldRoot>
37
+ </FieldsetRoot>
38
+ )
39
+ }
40
+ ```
41
+
42
+ ## Resources
43
+
44
+ - [Base UI Fieldset Documentation](https://base-ui.com/react/components/fieldset)
45
+ - [API Reference](https://base-ui.com/react/components/fieldset#api-reference)
@@ -30,6 +30,7 @@
30
30
  - Menu
31
31
  - Input
32
32
  - Form
33
+ - Fieldset
33
34
  - Field
34
35
  - Drawer
35
36
  - Dialog
@@ -59,6 +60,17 @@ If you have access to MCP tools, use the `compose-ui` MCP server:
59
60
  - Use built-in variant props (e.g., `variant="outline"`, `size="sm"`) for different appearances
60
61
  - For theming, use CSS variables - don't override component styles directly
61
62
 
63
+ **Form Building**
64
+ - When building forms, **always consult the Form component documentation** for complete examples
65
+ - Use `get_component({ slug: "form" })` to retrieve form patterns before writing any form code
66
+ - The Form docs include 5 integration patterns: Default (useState), useActionState, Zod validation, React Hook Form, and TanStack Form
67
+ - Follow these component composition patterns:
68
+ - `FormRoot` wraps the entire form
69
+ - `FieldRoot` wraps each field with `FieldLabel`, `FieldControl`, `FieldDescription`, `FieldError`
70
+ - `FieldsetRoot` groups related fields (radio groups, checkbox groups)
71
+ - Use `FieldValidity` for custom validation messages
72
+ - Always use the `errors` prop on `FormRoot` for server-side validation errors
73
+
62
74
  **Best Practices**
63
75
  - Always refer to this library as "Compose UI", not "Base UI" (Base UI is the underlying primitive library)
64
76
  - Copy examples directly from the documentation - they are complete and working
@@ -8962,6 +8974,54 @@ export default function WithTanstackFormExample() {
8962
8974
 
8963
8975
  ---
8964
8976
 
8977
+ # Fieldset
8978
+
8979
+ Groups related form fields together with an accessible legend.
8980
+
8981
+ ## Installation
8982
+
8983
+ ```bash
8984
+ npm install @lglab/compose-ui
8985
+ ```
8986
+
8987
+ ## Import
8988
+
8989
+ ```tsx
8990
+ import { FieldsetRoot, FieldsetLegend } from '@lglab/compose-ui'
8991
+ ```
8992
+
8993
+ ## Examples
8994
+
8995
+ ### Default
8996
+
8997
+ ```tsx
8998
+ import { FieldControl, FieldLabel, FieldRoot } from '@lglab/compose-ui/field'
8999
+ import { FieldsetLegend, FieldsetRoot } from '@lglab/compose-ui/fieldset'
9000
+
9001
+ export default function DefaultExample() {
9002
+ return (
9003
+ <FieldsetRoot>
9004
+ <FieldsetLegend>Personal Information</FieldsetLegend>
9005
+ <FieldRoot name='firstName'>
9006
+ <FieldLabel>First name</FieldLabel>
9007
+ <FieldControl placeholder='John' />
9008
+ </FieldRoot>
9009
+ <FieldRoot name='lastName'>
9010
+ <FieldLabel>Last name</FieldLabel>
9011
+ <FieldControl placeholder='Doe' />
9012
+ </FieldRoot>
9013
+ </FieldsetRoot>
9014
+ )
9015
+ }
9016
+ ```
9017
+
9018
+ ## Resources
9019
+
9020
+ - [Base UI Fieldset Documentation](https://base-ui.com/react/components/fieldset)
9021
+ - [API Reference](https://base-ui.com/react/components/fieldset#api-reference)
9022
+
9023
+ ---
9024
+
8965
9025
  # Field
8966
9026
 
8967
9027
  Provides accessible labeling and validation for form controls.
package/dist/llms.txt CHANGED
@@ -24,6 +24,17 @@ If you have access to MCP tools, use the `compose-ui` MCP server:
24
24
  - Use built-in variant props (e.g., `variant="outline"`, `size="sm"`) for different appearances
25
25
  - For theming, use CSS variables - don't override component styles directly
26
26
 
27
+ **Form Building**
28
+ - When building forms, **always consult the Form component documentation** for complete examples
29
+ - Use `get_component({ slug: "form" })` to retrieve form patterns before writing any form code
30
+ - The Form docs include 5 integration patterns: Default (useState), useActionState, Zod validation, React Hook Form, and TanStack Form
31
+ - Follow these component composition patterns:
32
+ - `FormRoot` wraps the entire form
33
+ - `FieldRoot` wraps each field with `FieldLabel`, `FieldControl`, `FieldDescription`, `FieldError`
34
+ - `FieldsetRoot` groups related fields (radio groups, checkbox groups)
35
+ - Use `FieldValidity` for custom validation messages
36
+ - Always use the `errors` prop on `FormRoot` for server-side validation errors
37
+
27
38
  **Best Practices**
28
39
  - Always refer to this library as "Compose UI", not "Base UI" (Base UI is the underlying primitive library)
29
40
  - Copy examples directly from the documentation - they are complete and working
@@ -61,6 +72,7 @@ If you have access to MCP tools, use the `compose-ui` MCP server:
61
72
  - [Menu](https://compose-ui.dev/components/menu.md): A component that displays a list of options on a temporary surface.
62
73
  - [Input](https://compose-ui.dev/components/input.md): A single-line text input element.
63
74
  - [Form](https://compose-ui.dev/components/form.md): A native form element with consolidated error handling. Examples include useActionState, Zod schema validation, React Hook Form, and TanStack Form integrations.
75
+ - [Fieldset](https://compose-ui.dev/components/fieldset.md): Groups related form fields together with an accessible legend.
64
76
  - [Field](https://compose-ui.dev/components/field.md): Provides accessible labeling and validation for form controls.
65
77
  - [Drawer](https://compose-ui.dev/components/drawer.md): A panel that slides in from the edge of the screen, commonly used for navigation, filters, or supplementary content.
66
78
  - [Dialog](https://compose-ui.dev/components/dialog.md): A popup that opens on top of the entire page with a backdrop, commonly used for confirmations, forms, and important messages.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lglab/compose-ui-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "MCP server exposing compose-ui library documentation to AI tools",
5
5
  "author": "LGLab",
6
6
  "license": "MIT",