diff --git a/modules/mcp/README.md b/modules/mcp/README.md index 0f3b7d0431..fabf88bbcd 100644 --- a/modules/mcp/README.md +++ b/modules/mcp/README.md @@ -45,6 +45,35 @@ Returns Canvas Kit upgrade guide documentation (v9 through v14) as resource link Returns Canvas Kit design token documentation for migrating to `@workday/canvas-tokens-web`. +### `get-accessibility-guidelines` + +Returns Canvas Kit accessibility guidance resource links for a component, scenario, or both. This is +documentation guidance only; it does not scan code or pages, run automated accessibility tests, +certify WCAG conformance, or guarantee compliance. + +Parameters: + +- `component` (optional) -- Canvas Kit component/story slug, such as `checkbox`, `table`, or + `modal`. +- `scenario` (optional) -- Accessibility scenario slug, such as `forms`, `tables`, `popups`, or + `page-structure`. + +At least one of `component` or `scenario` is required. + +Examples: + +```json +{"scenario": "forms"} +``` + +```json +{"component": "table"} +``` + +```json +{"component": "checkbox", "scenario": "forms"} +``` + ### `fetch-component-documentation-example` Renders an interactive Canvas Kit component story inline for the user. Accepts a `story` parameter @@ -70,12 +99,23 @@ Markdown upgrade guides for Canvas Kit major versions (v9-v14). Design token migration guides, color palette, roles, contrast, and scale documentation. +### `docs://accessibility/*` + +Accessibility guidance documentation for Canvas Kit scenarios, including overview, forms, page +structure, tables, popups, ARIA live regions, headers, side panels, Windows High Contrast themes, and +color contrast. + ### `docs://examples/{slug}` Markdown documentation and inline code examples for each component. These are extracted from the MDX story files at build time, with `ExampleCodeBlock` references replaced by the actual source code of each example. +### `docs://examples/{slug}/accessibility` + +Accessibility section extracted from a component's MDX documentation when that component has a +non-empty `## Accessibility` section. + ### `ui://story/{slug}` Interactive HTML previews of Canvas Kit components, served as MCP App resources @@ -98,7 +138,8 @@ The build runs in stages via `npm run build`: writes `lib/stories-config.json` 2. **`build:apps`** -- compiles each MDX story into a self-contained single-file HTML app using Vite, bundling React, Emotion, Canvas Tokens CSS, and lightweight Storybook stubs -3. **`build:copy`** -- copies static resources (upgrade guides, token docs) into `dist/lib` +3. **`build:copy`** -- copies static resources (upgrade guides, token docs, accessibility docs) into + `dist/lib` 4. **`build:types`** -- generates TypeScript declarations 5. **`build:mcp`** -- bundles `lib/index.ts` and `lib/cli.ts` with esbuild diff --git a/modules/mcp/build/discover-stories.ts b/modules/mcp/build/discover-stories.ts index 6a6acf1959..055dec311f 100644 --- a/modules/mcp/build/discover-stories.ts +++ b/modules/mcp/build/discover-stories.ts @@ -13,6 +13,7 @@ interface StoryEntry { storybookUrl: string; mdxPath: string; mdxProse: string; + accessibilityProse: string; } function titleToStorybookPath(title: string): string { @@ -68,6 +69,25 @@ function extractMdxProse(mdxFilePath: string, exampleSources: Record /^#{1,6}\s+Accessibility\b/i.test(line.trim())); + + if (start === -1) { + return ''; + } + + const headingLevel = lines[start].trim().match(/^(#{1,6})\s+Accessibility\b/i)?.[1].length || 2; + const nextSameOrHigherHeading = new RegExp(`^#{1,${headingLevel}}\\s+`); + const end = lines.findIndex( + (line, index) => index > start && nextSameOrHigherHeading.test(line.trim()) + ); + return lines + .slice(start, end === -1 ? undefined : end) + .join('\n') + .trim(); +} + function findExampleSources(mdxFilePath: string): Record { const mdxDir = path.dirname(mdxFilePath); const examplesDir = path.join(mdxDir, 'examples'); @@ -76,7 +96,9 @@ function findExampleSources(mdxFilePath: string): Record { } const sources: Record = {}; - const entries = fs.readdirSync(examplesDir).filter(f => f.endsWith('.tsx') || f.endsWith('.ts')); + const entries = fs + .readdirSync(examplesDir) + .filter((f: string) => f.endsWith('.tsx') || f.endsWith('.ts')); for (const entry of entries) { const name = entry.replace(/\.(tsx?|ts)$/, ''); @@ -89,14 +111,14 @@ function findExampleSources(mdxFilePath: string): Record { function findMdxFile(storyFilePath: string): string | null { const dir = path.dirname(storyFilePath); const entries = fs.readdirSync(dir); - const mdxFiles = entries.filter(e => e.endsWith('.mdx')); + const mdxFiles = entries.filter((e: string) => e.endsWith('.mdx')); if (mdxFiles.length === 0) { return null; } const storyBaseName = path.basename(storyFilePath).replace(/\.stories\.(ts|tsx)$/, ''); - const exactMatch = mdxFiles.find(f => f.replace('.mdx', '') === storyBaseName); + const exactMatch = mdxFiles.find((f: string) => f.replace('.mdx', '') === storyBaseName); if (exactMatch) { return path.join(dir, exactMatch); } @@ -165,11 +187,13 @@ async function main() { const repoRoot = path.resolve(__dirname, '../../..'); const absoluteMdxPath = path.resolve(repoRoot, candidate.mdxPath); const exampleSources = findExampleSources(absoluteMdxPath); + const mdxProse = extractMdxProse(absoluteMdxPath, exampleSources); stories[slug] = { title: candidate.title, storybookUrl: `${STORYBOOK_BASE_URL}?path=/docs/${storybookPath}--docs`, mdxPath: candidate.mdxPath, - mdxProse: extractMdxProse(absoluteMdxPath, exampleSources), + mdxProse, + accessibilityProse: extractAccessibilitySection(mdxProse), }; } diff --git a/modules/mcp/build/index.ts b/modules/mcp/build/index.ts index 2a89c78520..eb19fb8679 100644 --- a/modules/mcp/build/index.ts +++ b/modules/mcp/build/index.ts @@ -9,14 +9,21 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const llmSourceDir = path.resolve(__dirname, '../../docs/llm'); +const accessibilitySourceDir = path.resolve(__dirname, '../../docs/mdx'); const targetDir = path.resolve(__dirname, '../dist/lib'); +type AccessibilityFileEntry = + | string + | { + source: string; + slug: string; + }; + /** * Copy a specific file from source to destination, creating directories as needed */ -function copyFile(relativePath: string): void { - // All files are now in the llm source directory - const srcPath = path.resolve(llmSourceDir, relativePath); +function copyFile(sourceDir: string, relativePath: string): void { + const srcPath = path.resolve(sourceDir, relativePath); const destPath = path.resolve(targetDir, relativePath); // Check if source file exists @@ -34,14 +41,24 @@ function copyFile(relativePath: string): void { fs.copyFileSync(srcPath, destPath); } -// Get file list from index.json and copy only those files -// Combine upgradeGuideFiles and tokenFiles, removing duplicates const allFiles = [...new Set([...index.upgradeGuideFiles, ...index.tokenFiles])]; +const accessibilityFiles = [ + ...new Map( + (index.accessibilityFiles as AccessibilityFileEntry[]).map(file => { + const source = typeof file === 'string' ? file : file.source; + return [source, {source}] as const; + }) + ).values(), +]; -console.log(`Found ${allFiles.length} files to copy:`); +console.log(`Found ${allFiles.length + accessibilityFiles.length} files to copy:`); allFiles.forEach(file => console.log(` - ${file}`)); +accessibilityFiles.forEach(file => console.log(` - ${file.source}`)); -allFiles.forEach(file => copyFile(file)); +allFiles.forEach(file => copyFile(llmSourceDir, file)); +accessibilityFiles.forEach(file => { + copyFile(accessibilitySourceDir, file.source); +}); // story-viewer.html is now built by build-story-apps.ts through Vite (not copied raw). diff --git a/modules/mcp/lib/accessibility-enums.ts b/modules/mcp/lib/accessibility-enums.ts new file mode 100644 index 0000000000..82039847b8 --- /dev/null +++ b/modules/mcp/lib/accessibility-enums.ts @@ -0,0 +1,176 @@ +export const ACCESSIBILITY_SCENARIOS = [ + 'overview', + 'page-structure', + 'tables', + 'popups', + 'aria-live', + 'headers', + 'side-panel', + 'windows-high-contrast', + 'forms', + 'color-contrast', +] as const; + +export type AccessibilityScenario = (typeof ACCESSIBILITY_SCENARIOS)[number]; + +export const ACCESSIBILITY_COMPONENTS = [ + 'action-bar', + 'ai-ingress-button-(ai)', + 'avatar-(promoted)', + 'banner', + 'body-text', + 'box', + 'breadcrumbs', + 'buttons', + 'card', + 'checkbox', + 'color-input', + 'color-picker', + 'color-preview', + 'countbadge', + 'dialog', + 'divider', + 'expandable', + 'flex', + 'form-field', + 'grid', + 'heading', + 'hyperlink', + 'information-highlight', + 'loading-dots', + 'loading-sparkles-(ai)', + 'menu', + 'modal', + 'multi-select', + 'pagination', + 'pill', + 'popper', + 'popup', + 'radio', + 'radio-(deprecated)', + 'segmented-control', + 'select', + 'side-panel-(deprecated)', + 'side-panel-(new)', + 'skeleton', + 'status-indicator', + 'status-indicator-(deprecated)', + 'subtext', + 'switch-(new)', + 'switch-(deprecated)', + 'table', + 'tabs', + 'text', + 'text-area', + 'text-input', + 'title', + 'toast', + 'toolbar', + 'tooltip', +] as const; + +export type AccessibilityComponent = (typeof ACCESSIBILITY_COMPONENTS)[number]; + +const FORM_COMPONENTS = new Set([ + 'checkbox', + 'color-input', + 'color-picker', + 'color-preview', + 'form-field', + 'multi-select', + 'radio', + 'radio-(deprecated)', + 'segmented-control', + 'select', + 'switch-(new)', + 'switch-(deprecated)', + 'text-area', + 'text-input', +]); + +const POPUP_COMPONENTS = new Set(['dialog', 'menu', 'modal', 'popper', 'popup', 'tooltip']); + +const STRUCTURE_COMPONENTS = new Set([ + 'box', + 'breadcrumbs', + 'card', + 'divider', + 'expandable', + 'flex', + 'grid', + 'heading', + 'hyperlink', + 'pagination', + 'side-panel-(deprecated)', + 'side-panel-(new)', + 'tabs', +]); + +const STATUS_COMPONENTS = new Set([ + 'ai-ingress-button-(ai)', + 'avatar-(promoted)', + 'banner', + 'body-text', + 'countbadge', + 'information-highlight', + 'loading-dots', + 'loading-sparkles-(ai)', + 'pill', + 'skeleton', + 'status-indicator', + 'status-indicator-(deprecated)', + 'subtext', + 'text', + 'title', + 'toast', + 'toolbar', + 'action-bar', +]); + +export function getAccessibilityScenarioSlugsForComponent( + component: string +): AccessibilityScenario[] { + if (FORM_COMPONENTS.has(component)) { + return ['forms', 'overview']; + } + + if (POPUP_COMPONENTS.has(component)) { + return ['popups', 'overview']; + } + + if (component === 'table') { + return ['tables', 'overview']; + } + + if (STRUCTURE_COMPONENTS.has(component)) { + return ['page-structure', 'overview']; + } + + if (STATUS_COMPONENTS.has(component)) { + return ['aria-live', 'color-contrast', 'overview']; + } + + return ['overview']; +} + +export function resolveAccessibilityScenarioSlugs({ + component, + scenario, +}: { + component?: string; + scenario?: AccessibilityScenario; +}): AccessibilityScenario[] { + if (component && scenario) { + return [...new Set([...getAccessibilityScenarioSlugsForComponent(component), scenario])]; + } + + if (component) { + return getAccessibilityScenarioSlugsForComponent(component); + } + + if (scenario) { + return [scenario]; + } + + return []; +} diff --git a/modules/mcp/lib/config.json b/modules/mcp/lib/config.json index 54cf41ff53..da917c1e42 100644 --- a/modules/mcp/lib/config.json +++ b/modules/mcp/lib/config.json @@ -23,5 +23,47 @@ "tokens/v4/shape.md", "tokens/v4/size.md", "tokens/v4/space.md" + ], + "accessibilityFiles": [ + { + "source": "accessibility/AccessibilityOverview.mdx", + "slug": "overview" + }, + { + "source": "accessibility/PageStructure.mdx", + "slug": "page-structure" + }, + { + "source": "accessibility/TablesAdvanced.mdx", + "slug": "tables" + }, + { + "source": "accessibility/Popups.mdx", + "slug": "popups" + }, + { + "source": "accessibility/AriaLiveRegions.mdx", + "slug": "aria-live" + }, + { + "source": "accessibility/Headers.mdx", + "slug": "headers" + }, + { + "source": "accessibility/SidePanel.mdx", + "slug": "side-panel" + }, + { + "source": "accessibility/WindowsHighContrastThemes.mdx", + "slug": "windows-high-contrast" + }, + { + "source": "accessibility/TestingTableWithFormFields.mdx", + "slug": "forms" + }, + { + "source": "accessibility/WindowsHighContrastThemes.mdx", + "slug": "color-contrast" + } ] } diff --git a/modules/mcp/lib/index.ts b/modules/mcp/lib/index.ts index 0e505a6a19..fe666bf4a5 100644 --- a/modules/mcp/lib/index.ts +++ b/modules/mcp/lib/index.ts @@ -11,6 +11,11 @@ import {fileURLToPath} from 'node:url'; import {z} from 'zod'; import packageJson from '../package.json'; +import { + ACCESSIBILITY_COMPONENTS, + ACCESSIBILITY_SCENARIOS, + resolveAccessibilityScenarioSlugs, +} from './accessibility-enums'; import fileNames from './config.json'; import storiesConfig from './stories-config.json'; @@ -34,6 +39,98 @@ export function getServer() { } ); + const accessibilityResourceMetadata: Record< + string, + {title: string; description: string; slug: string} + > = { + 'accessibility/AccessibilityOverview.mdx': { + title: 'Canvas Kit Accessibility Overview', + description: + 'Core Canvas Kit accessibility principles and component usage guidance. Guidance only; no automated validation or compliance certification.', + slug: 'overview', + }, + 'accessibility/PageStructure.mdx': { + title: 'Canvas Kit Page Structure Accessibility', + description: + 'Guidance for landmarks, headings, navigation, and logical focus order in Canvas Kit applications.', + slug: 'page-structure', + }, + 'accessibility/TablesAdvanced.mdx': { + title: 'Canvas Kit Table Accessibility', + description: + 'Guidance for semantic data tables, interactive tables, sorting, filtering, row selection, and table focus behavior.', + slug: 'tables', + }, + 'accessibility/Popups.mdx': { + title: 'Canvas Kit Popup and Overlay Accessibility', + description: + 'Guidance for dialogs, modals, popups, menus, tooltips, focus management, dismissal, and reading order.', + slug: 'popups', + }, + 'accessibility/AriaLiveRegions.mdx': { + title: 'Canvas Kit ARIA Live Region Accessibility', + description: + 'Guidance for announcing asynchronous status updates with polite and assertive live regions.', + slug: 'aria-live', + }, + 'accessibility/Headers.mdx': { + title: 'Canvas Kit Header Accessibility', + description: + 'Guidance for accessible application headers, page headers, heading text, navigation, and header controls.', + slug: 'headers', + }, + 'accessibility/SidePanel.mdx': { + title: 'Canvas Kit Side Panel Accessibility', + description: + 'Guidance for side panel semantics, focus behavior, naming, and overlay versus persistent panel patterns.', + slug: 'side-panel', + }, + 'accessibility/WindowsHighContrastThemes.mdx': { + title: 'Canvas Kit Windows High Contrast Accessibility', + description: + 'Guidance for forced colors and Windows High Contrast themes, including focus, state, border, and icon visibility.', + slug: 'windows-high-contrast', + }, + 'accessibility/TestingTableWithFormFields.mdx': { + title: 'Canvas Kit Form Accessibility', + description: + 'Guidance from existing Canvas Kit accessibility documentation for form fields in table contexts.', + slug: 'forms', + }, + 'accessibility/WindowsHighContrastThemes.mdx#color-contrast': { + title: 'Canvas Kit Color Contrast Accessibility', + description: + 'Guidance from existing Canvas Kit accessibility documentation for contrast-sensitive UI behavior in high contrast themes.', + slug: 'color-contrast', + }, + }; + + function getAccessibilityFileEntry(fileEntry: string | {source: string; slug: string}) { + return typeof fileEntry === 'string' + ? { + source: fileEntry, + slug: accessibilityResourceMetadata[fileEntry]?.slug, + } + : fileEntry; + } + + function getAccessibilityResource(fileEntry: string | {source: string; slug: string}) { + const entry = getAccessibilityFileEntry(fileEntry); + const metadataKey = + entry.slug === 'color-contrast' ? `${entry.source}#color-contrast` : entry.source; + const metadata = accessibilityResourceMetadata[metadataKey]; + if (!metadata) { + throw new Error(`${entry.source} is not a valid accessibility resource`); + } + + return { + ...metadata, + mimeType: 'text/markdown', + uri: `docs://accessibility/${entry.slug}`, + contents: fs.readFileSync(path.resolve(__dirname, 'lib', entry.source), 'utf8'), + }; + } + /** * Metadata for agents about the resource files. */ @@ -490,11 +587,36 @@ Returns links to token documentation resources including migration guides, color } ); + fileNames.accessibilityFiles.forEach(fileName => { + const resource = getAccessibilityResource(fileName); + if (!resource || !resource.contents) { + throw new Error(`Resource ${fileName} not found`); + } + server.registerResource( + resource.title, + resource.uri, + { + title: resource.title, + description: resource.description, + mimeType: resource.mimeType, + }, + async (uri: URL) => ({ + contents: [ + { + uri: uri.href, + text: resource.contents, + }, + ], + }) + ); + }); + interface StoryConfig { title: string; storybookUrl: string; mdxPath: string; mdxProse: string; + accessibilityProse?: string; } const stories = storiesConfig.stories as Record; @@ -551,8 +673,150 @@ Returns links to token documentation resources including migration guides, color }) ); } + + if (story.accessibilityProse?.trim()) { + server.registerResource( + `${story.title} Accessibility Guidance`, + `docs://examples/${slug}/accessibility`, + { + title: `${story.title} Accessibility Guidance`, + description: `Accessibility section extracted from the ${story.title} component documentation. Guidance only; this is not automated accessibility validation.`, + mimeType: 'text/markdown', + }, + async (uri: URL) => ({ + contents: [ + { + uri: uri.href, + text: story.accessibilityProse, + }, + ], + }) + ); + } } + const getAccessibilityResourceBySlug = (slug: string) => { + const fileName = fileNames.accessibilityFiles.find(file => { + const resource = getAccessibilityResource(file); + return resource.slug === slug; + }); + + if (!fileName) { + throw new Error(`Accessibility resource not found for slug "${slug}"`); + } + + return getAccessibilityResource(fileName); + }; + + server.registerTool( + 'get-accessibility-guidelines', + { + title: 'Get Canvas Kit Accessibility Guidelines', + description: + 'Retrieve Canvas Kit accessibility guidance resources for a scenario, component, or both. This tool returns documentation links only; it does not scan code, test pages, certify WCAG conformance, or guarantee accessibility compliance.', + inputSchema: z + .object({ + component: z + .enum(ACCESSIBILITY_COMPONENTS) + .optional() + .describe('Canvas Kit component or story slug to retrieve accessibility guidance for'), + scenario: z + .enum(ACCESSIBILITY_SCENARIOS) + .optional() + .describe('Accessibility scenario slug to retrieve guidance for'), + }) + .refine(data => !!data.component || !!data.scenario, { + message: 'At least one of "component" or "scenario" is required.', + }), + annotations: { + readOnlyHint: true, + }, + }, + async ({component, scenario}: {component?: string; scenario?: string}) => { + const scenarioSlugs = resolveAccessibilityScenarioSlugs({component, scenario}); + const accessibilityResources = scenarioSlugs.map(slug => { + const resource = getAccessibilityResourceBySlug(slug); + return { + uri: resource.uri, + title: resource.title, + description: resource.description, + }; + }); + + const componentStory = component ? stories[component] : null; + const exampleDocumentation = + component && componentStory?.mdxProse + ? { + uri: `docs://examples/${component}`, + title: `${componentStory.title} Documentation & Sample Code`, + description: `Documentation and code examples for ${componentStory.title}.`, + } + : null; + const componentAccessibilityDocumentation = + component && componentStory?.accessibilityProse?.trim() + ? { + uri: `docs://examples/${component}/accessibility`, + title: `${componentStory.title} Accessibility Guidance`, + description: `Accessibility section extracted from the ${componentStory.title} component documentation. Guidance only; this is not automated accessibility validation.`, + } + : null; + + const output = { + component: component || null, + scenario: scenario || null, + scenarioSlugs, + accessibilityResources, + componentAccessibilityDocumentation, + exampleDocumentation, + }; + + return { + content: [ + {type: 'text' as const, text: JSON.stringify(output)}, + ...accessibilityResources.map(resource => ({ + type: 'resource_link' as const, + uri: resource.uri, + name: resource.title, + mimeType: 'text/markdown', + description: resource.description, + annotations: { + audience: ['user', 'assistant'] as ('user' | 'assistant')[], + }, + })), + ...(exampleDocumentation + ? [ + { + type: 'resource_link' as const, + uri: exampleDocumentation.uri, + name: exampleDocumentation.title, + mimeType: 'text/markdown', + description: exampleDocumentation.description, + annotations: { + audience: ['user', 'assistant'] as ('user' | 'assistant')[], + }, + }, + ] + : []), + ...(componentAccessibilityDocumentation + ? [ + { + type: 'resource_link' as const, + uri: componentAccessibilityDocumentation.uri, + name: componentAccessibilityDocumentation.title, + mimeType: 'text/markdown', + description: componentAccessibilityDocumentation.description, + annotations: { + audience: ['user', 'assistant'] as ('user' | 'assistant')[], + }, + }, + ] + : []), + ], + structuredContent: output, + }; + } + ); + const storyViewerPath = path.resolve(__dirname, 'apps', 'story-viewer.html'); if (storySlugs.length > 0 && fs.existsSync(storyViewerPath)) { const slugEnum = storySlugs as [string, ...string[]]; diff --git a/modules/mcp/stories/mdx/MCPDocs.mdx b/modules/mcp/stories/mdx/MCPDocs.mdx index 13d74e6f08..13a81a38eb 100644 --- a/modules/mcp/stories/mdx/MCPDocs.mdx +++ b/modules/mcp/stories/mdx/MCPDocs.mdx @@ -4,7 +4,7 @@ import { Meta } from '@storybook/blocks'; # Canvas Kit MCP -This Model Context Protocol (MCP) server exposes Canvas Kit **upgrade guides**, **design token** documentation, and **per-component docs with sample code** (plus optional interactive previews) to AI assistants. +This Model Context Protocol (MCP) server exposes Canvas Kit **upgrade guides**, **design token** documentation, **accessibility guidance**, and **per-component docs with sample code** (plus optional interactive previews) to AI assistants. ## Table of Contents @@ -16,11 +16,14 @@ This Model Context Protocol (MCP) server exposes Canvas Kit **upgrade guides**, - [Tools](#tools) - [`get-canvas-kit-upgrade-guides`](#get-canvas-kit-upgrade-guides) - [`get-canvas-kit-tokens`](#get-canvas-kit-tokens) + - [`get-accessibility-guidelines`](#get-accessibility-guidelines) - [`fetch-component-documentation-example`](#fetch-component-documentation-example) - [Resources](#resources) - [Upgrade Guides](#upgrade-guides) - [Token Documentation](#token-documentation) + - [Accessibility Guidance (`docs://accessibility/*`)](#accessibility-guidance-docsaccessibility) - [Component examples (`docs://examples/{slug}`)](#component-examples-docsexamplesslug) + - [Component accessibility sections (`docs://examples/{slug}/accessibility`)](#component-accessibility-sections-docsexamplesslugaccessibility) - [Interactive story previews (`ui://story/{slug}`)](#interactive-story-previews-uistoryslug) - [How assistants should use tools vs resources](#how-assistants-should-use-tools-vs-resources) - [Source Documentation](#source-documentation) @@ -160,6 +163,33 @@ Retrieves Canvas Kit design token migration documentation for transitioning to t **Returns:** Links to token documentation including migration guides, color palettes, color roles, contrast guidelines, and token system references. +### `get-accessibility-guidelines` + +Retrieves Canvas Kit accessibility guidance for a scenario, component, or both. This tool is **documentation guidance only**. It does not scan code or pages, run automated accessibility tests, certify WCAG conformance, or guarantee compliance. + +**Parameters:** + +- `component` (optional) — Canvas Kit component/story slug, such as `checkbox`, `table`, or `modal`. +- `scenario` (optional) — Accessibility scenario slug, such as `forms`, `tables`, `popups`, `aria-live`, or `page-structure`. + +At least one of `component` or `scenario` is required. + +**Examples:** + +```json +{"scenario": "forms"} +``` + +```json +{"component": "table"} +``` + +```json +{"component": "checkbox", "scenario": "forms"} +``` + +**Returns:** A JSON summary plus `resource_link` entries for matching `docs://accessibility/{slug}` resources. When available, the response also includes `docs://examples/{component}` and `docs://examples/{component}/accessibility`. + ### `fetch-component-documentation-example` Opens a **Canvas Kit component** in Storybook (URL returned to the user) and returns metadata for assistants, including a link to the full markdown doc for that component. @@ -208,10 +238,26 @@ Design token migration and usage guides covering: - Spacing, shape, size, opacity, and depth tokens - OKLCH color space implementation +### Accessibility Guidance (`docs://accessibility/*`) + +URI prefix: `docs://accessibility/*` + +Guidance-only accessibility documentation covering: + +- Overview and general Canvas Kit accessibility principles +- Forms, page structure, tables, popups, ARIA live regions, headers, and side panels +- Windows High Contrast themes and color contrast + +These resources do not perform automated validation or provide compliance guarantees. + ### Component examples (`docs://examples/{slug}`) Markdown documentation and **inline code** for each discovered component story. Content is produced at build time from MDX story files (with `ExampleCodeBlock` replaced by real example source). Use these when implementing or explaining APIs. +### Component accessibility sections (`docs://examples/{slug}/accessibility`) + +Markdown extracted from a component's exact `## Accessibility` section when that section exists in the component MDX docs. Use these with the broader `docs://accessibility/*` resources for component-specific guidance. + ### Interactive story previews (`ui://story/{slug}`) Self-contained HTML apps for inline preview where the client supports MCP App resources (`text/html;profile=mcp-app`). @@ -226,6 +272,7 @@ All resources are available to AI assistants through MCP resource URIs. ## Source Documentation - **Upgrade guides and token LLM docs** live under [`modules/docs/llm`](../../docs/llm) (upgrade guides, token guides, and other LLM-oriented files). Update those files and rebuild the MCP package to refresh bundled resources. +- **Accessibility docs** live under [`modules/docs/mdx/accessibility`](../../docs/mdx/accessibility) and are copied into the MCP package through the `accessibilityFiles` manifest. - **Component example markdown** is generated from Canvas Kit story MDX and example files under `modules/react` and `modules/preview-react` via the MCP build (`build:discover`, `build:apps`). See [`modules/mcp/README.md`](../../README.md) for the pipeline. ## Contributing