diff --git a/.cursor/flows/ds-refactoring-flow/01-find-violations.mdc b/.cursor/flows/ds-refactoring-flow/01-find-violations.mdc index 9cee81b..255899c 100644 --- a/.cursor/flows/ds-refactoring-flow/01-find-violations.mdc +++ b/.cursor/flows/ds-refactoring-flow/01-find-violations.mdc @@ -19,7 +19,10 @@ Step 1: Find violations Store the result in a variable called scanResult. 2. Perform first-level error handling: - - If the function call returns an error, respond with: + - If the function call returns an error or result containing "Missing ds.deprecatedCssClassesPath", respond with: + ⚠️ *Cannot proceed: Missing required configuration parameter* – The `ds.deprecatedCssClassesPath` parameter must be provided when starting the MCP server to use violation detection tools. Please restart the server with this parameter configured. + Then stop execution. + - If the function call returns any other error, respond with: 🚨 *Tool execution failed* – [error message] Then stop execution. - If scanResult.totalViolations is 0, respond with: @@ -57,7 +60,10 @@ Once the user provides a subfolder choice, proceed as follows: - Store the result in a variable called fileScan. 3. Perform error handling and validation: - - If the function call returns an error, respond with: + - If the function call returns an error containing "Missing ds.deprecatedCssClassesPath", respond with: + ⚠️ *Cannot proceed: Missing required configuration parameter* – The `ds.deprecatedCssClassesPath` parameter must be provided when starting the MCP server to use violation detection tools. Please restart the server with this parameter configured. + Then stop execution. + - If the function call returns any other error, respond with: 🚨 *Tool execution failed* – [error message] Then stop execution. - If fileScan.rows.length is 0, respond with: diff --git a/.cursor/flows/ds-refactoring-flow/01b-find-all-violations.mdc b/.cursor/flows/ds-refactoring-flow/01b-find-all-violations.mdc index 714da1b..0ee4864 100644 --- a/.cursor/flows/ds-refactoring-flow/01b-find-all-violations.mdc +++ b/.cursor/flows/ds-refactoring-flow/01b-find-all-violations.mdc @@ -17,7 +17,10 @@ Step 1: Find violations Store the result in a variable called scanResult. 2. Perform first-level error handling: - - If the function call returns an error, respond with: + - If the function call returns an error or result containing "Missing ds.deprecatedCssClassesPath", respond with: + ⚠️ *Cannot proceed: Missing required configuration parameter* – The `ds.deprecatedCssClassesPath` parameter must be provided when starting the MCP server to use violation detection tools. Please restart the server with this parameter configured. + Then stop execution. + - If the function call returns any other error, respond with: 🚨 *Tool execution failed* – [error message] Then stop execution. - If no violations are found, respond with: @@ -54,7 +57,10 @@ Once the user provides a subfolder choice, proceed as follows: - Store the result in a variable called fileScan. 3. Perform error handling and validation: - - If the function call returns an error, respond with: + - If the function call returns an error containing "Missing ds.deprecatedCssClassesPath", respond with: + ⚠️ *Cannot proceed: Missing required configuration parameter* – The `ds.deprecatedCssClassesPath` parameter must be provided when starting the MCP server to use violation detection tools. Please restart the server with this parameter configured. + Then stop execution. + - If the function call returns any other error, respond with: 🚨 *Tool execution failed* – [error message] Then stop execution. - If fileScan.rows.length is 0, respond with: diff --git a/.cursor/mcp.json b/.cursor/mcp.json deleted file mode 100644 index dc83135..0000000 --- a/.cursor/mcp.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "mcpServers": { - "nx-mcp": { - "url": "http://localhost:9665/sse" - }, - "angular-toolkit-mcp": { - "command": "node", - "args": [ - "./packages/angular-mcp/dist/main.js", - "--workspaceRoot=/home/spoltorak/projects/x-mcp", - "--ds.storybookDocsRoot=packages/minimal-repo/packages/design-system/storybook-host-app/src/components", - "--ds.deprecatedCssClassesPath=packages/minimal-repo/packages/design-system/component-options.js", - "--ds.uiRoot=packages/minimal-repo/packages/design-system/ui" - ] - }, - "ESLint": { - "type": "stdio", - "command": "npx", - "args": ["@eslint/mcp@latest"] - } - } -} diff --git a/README.md b/README.md index 11e3a4b..8e943fc 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,8 @@ Copy `.cursor/mcp.json.example` to the project you're working on. Copied file sh } ``` +> Note: `ds.storybookDocsRoot` and `ds.deprecatedCssClassesPath` are optional. The server will start without them. Tools that require these paths will return a clear error prompting you to provide the missing parameter. + > **Note**: The example file contains configuration for `ESLint` official MCP which is required for the toolkit to work properly. ### Configuration Parameters @@ -76,10 +78,20 @@ Copy `.cursor/mcp.json.example` to the project you're working on. Copied file sh | Parameter | Type | Description | Example | |-----------|------|-------------|---------| | `workspaceRoot` | Absolute path | Root directory of your Angular workspace | `/Users/dev/my-angular-app` | -| `ds.storybookDocsRoot` | Relative path | Root directory containing Storybook documentation | `storybook/docs` | -| `ds.deprecatedCssClassesPath` | Relative path | JavaScript file mapping deprecated CSS classes | `design-system/component-options.js` | | `ds.uiRoot` | Relative path | Directory containing UI components | `packages/ui` | +#### Optional Parameters + +| Parameter | Type | Description | Example | +|-----------|------|-------------|---------| +| `ds.storybookDocsRoot` | Relative path | Root directory containing Storybook documentation used by documentation-related tools | `storybook/docs` | +| `ds.deprecatedCssClassesPath` | Relative path | JavaScript file mapping deprecated CSS classes used by violation and deprecated CSS tools | `design-system/component-options.js` | + +When optional parameters are omitted: + +- `ds.storybookDocsRoot`: Tools will skip Storybook documentation lookups (e.g., `get-ds-component-data` will still return implementation/import data but may have no docs files). +- `ds.deprecatedCssClassesPath`: Tools that require the mapping will fail fast with a clear error. Affected tools include: `get-deprecated-css-classes`, `report-deprecated-css`, `report-all-violations`, and `report-violations`. + #### Deprecated CSS Classes File Format The `component-options.js` file should export an array of component configurations: @@ -141,6 +153,29 @@ my-angular-workspace/ - **`build-component-usage-graph`**: Maps where given Angular components are imported (modules, specs, templates, styles) so refactors touch every file +### Tool behavior with optional parameters + +The following tools work without optional params: + +- `get-project-dependencies` +- `build-component-usage-graph` +- `get-ds-component-data` (documentation section is empty if `ds.storybookDocsRoot` is not set) +- Component contract tools: + - `build_component_contract` + - `diff_component_contract` + - `list_component_contracts` + +The following tools require optional params to work: + +- Requires `ds.deprecatedCssClassesPath`: + - `get-deprecated-css-classes` + - `report-deprecated-css` + - `report-all-violations` + - `report-violations` + +- Requires `ds.storybookDocsRoot` for docs lookup (skipped otherwise): + - `get-ds-component-data` (docs files discovery only) + ### Component Contracts - **`build_component_contract`**: Generate a static surface contract for a component's template and SCSS diff --git a/packages/angular-mcp-server/src/lib/angular-mcp-server.ts b/packages/angular-mcp-server/src/lib/angular-mcp-server.ts index 8b7857f..11146f8 100644 --- a/packages/angular-mcp-server/src/lib/angular-mcp-server.ts +++ b/packages/angular-mcp-server/src/lib/angular-mcp-server.ts @@ -25,8 +25,8 @@ import { validateDeprecatedCssClassesFile } from './validation/ds-components-fil export class AngularMcpServerWrapper { private readonly mcpServer: McpServer; private readonly workspaceRoot: string; - private readonly storybookDocsRoot: string; - private readonly deprecatedCssClassesPath: string; + private readonly storybookDocsRoot?: string; + private readonly deprecatedCssClassesPath?: string; private readonly uiRoot: string; /** @@ -71,11 +71,13 @@ export class AngularMcpServerWrapper { // Validate config using the Zod schema - only once here const validatedConfig = AngularMcpServerOptionsSchema.parse(config); - // Validate file existence + // Validate file existence (optional keys are checked only when provided) validateAngularMcpServerFilesExist(validatedConfig); - // Load and validate deprecatedCssClassesPath content - await validateDeprecatedCssClassesFile(validatedConfig); + // Load and validate deprecatedCssClassesPath content only if provided + if (validatedConfig.ds.deprecatedCssClassesPath) { + await validateDeprecatedCssClassesFile(validatedConfig); + } return new AngularMcpServerWrapper(validatedConfig); } @@ -140,6 +142,12 @@ export class AngularMcpServerWrapper { // Scan available design system components to add them as discoverable resources try { + if (!this.storybookDocsRoot) { + return { + resources, + }; + } + const dsUiPath = path.resolve( process.cwd(), this.storybookDocsRoot, diff --git a/packages/angular-mcp-server/src/lib/tools/ds/component/get-deprecated-css-classes.tool.ts b/packages/angular-mcp-server/src/lib/tools/ds/component/get-deprecated-css-classes.tool.ts index 1fb33a1..ceacbf4 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/component/get-deprecated-css-classes.tool.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/component/get-deprecated-css-classes.tool.ts @@ -28,6 +28,11 @@ export const getDeprecatedCssClassesHandler = createHandler< >( getDeprecatedCssClassesSchema.name, async ({ componentName }, { cwd, deprecatedCssClassesPath }) => { + if (!deprecatedCssClassesPath) { + throw new Error( + 'Missing ds.deprecatedCssClassesPath. Provide --ds.deprecatedCssClassesPath in mcp.json file.', + ); + } return getDeprecatedCssClasses( componentName, deprecatedCssClassesPath, diff --git a/packages/angular-mcp-server/src/lib/tools/ds/component/get-ds-component-data.tool.ts b/packages/angular-mcp-server/src/lib/tools/ds/component/get-ds-component-data.tool.ts index 43e3049..4ae1c44 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/component/get-ds-component-data.tool.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/component/get-ds-component-data.tool.ts @@ -137,31 +137,34 @@ export const getDsComponentDataHandler = createHandler< } const documentationFiles: string[] = []; - if (includeDocumentation) { + + let storiesFilePaths: string[] = []; + if (storybookDocsRoot) { const docsBasePath = resolveCrossPlatformPath(cwd, storybookDocsRoot); - const docPaths = getComponentDocPathsForName( - docsBasePath, - componentName, - ); - if (fs.existsSync(docPaths.paths.api)) { - documentationFiles.push(`file://${docPaths.paths.api}`); + if (includeDocumentation) { + const docPaths = getComponentDocPathsForName( + docsBasePath, + componentName, + ); + + if (fs.existsSync(docPaths.paths.api)) { + documentationFiles.push(`file://${docPaths.paths.api}`); + } + if (fs.existsSync(docPaths.paths.overview)) { + documentationFiles.push(`file://${docPaths.paths.overview}`); + } } - if (fs.existsSync(docPaths.paths.overview)) { - documentationFiles.push(`file://${docPaths.paths.overview}`); - } - } - let storiesFilePaths: string[] = []; - if (includeStories) { - const docsBasePath = resolveCrossPlatformPath(cwd, storybookDocsRoot); - const componentFolderName = componentNameToKebabCase(componentName); - const storiesComponentFolderPath = path.join( - docsBasePath, - componentFolderName, - ); - const storiesFiles = findStoriesFiles(storiesComponentFolderPath); - storiesFilePaths = storiesFiles.map((file) => `file://${file}`); + if (includeStories) { + const componentFolderName = componentNameToKebabCase(componentName); + const storiesComponentFolderPath = path.join( + docsBasePath, + componentFolderName, + ); + const storiesFiles = findStoriesFiles(storiesComponentFolderPath); + storiesFilePaths = storiesFiles.map((file) => `file://${file}`); + } } return { diff --git a/packages/angular-mcp-server/src/lib/tools/ds/component/list-ds-components.tool.ts b/packages/angular-mcp-server/src/lib/tools/ds/component/list-ds-components.tool.ts index d657a35..0169511 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/component/list-ds-components.tool.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/component/list-ds-components.tool.ts @@ -150,7 +150,6 @@ export const listDsComponentsHandler = createHandler< }); const components: DsComponentInfo[] = []; - const docsBasePath = resolveCrossPlatformPath(cwd, storybookDocsRoot); const includeAll = sections.includes('all'); const includeImplementation = @@ -172,28 +171,36 @@ export const listDsComponentsHandler = createHandler< } const documentationFiles: string[] = []; - if (includeDocumentation) { - const docPaths = getComponentDocPathsForName( - docsBasePath, - componentName, + + let storiesFilePaths: string[] = []; + if (storybookDocsRoot) { + const docsBasePath = resolveCrossPlatformPath( + cwd, + storybookDocsRoot, ); - if (fs.existsSync(docPaths.paths.api)) { - documentationFiles.push(`file://${docPaths.paths.api}`); + if (includeDocumentation) { + const docPaths = getComponentDocPathsForName( + docsBasePath, + componentName, + ); + + if (fs.existsSync(docPaths.paths.api)) { + documentationFiles.push(`file://${docPaths.paths.api}`); + } + if (fs.existsSync(docPaths.paths.overview)) { + documentationFiles.push(`file://${docPaths.paths.overview}`); + } } - if (fs.existsSync(docPaths.paths.overview)) { - documentationFiles.push(`file://${docPaths.paths.overview}`); - } - } - let storiesFilePaths: string[] = []; - if (includeStories) { - const storiesComponentFolderPath = path.join( - docsBasePath, - folderName, - ); - const storiesFiles = findStoriesFiles(storiesComponentFolderPath); - storiesFilePaths = storiesFiles.map((file) => `file://${file}`); + if (includeStories) { + const storiesComponentFolderPath = path.join( + docsBasePath, + folderName, + ); + const storiesFiles = findStoriesFiles(storiesComponentFolderPath); + storiesFilePaths = storiesFiles.map((file) => `file://${file}`); + } } components.push({ diff --git a/packages/angular-mcp-server/src/lib/tools/ds/project/report-deprecated-css.tool.ts b/packages/angular-mcp-server/src/lib/tools/ds/project/report-deprecated-css.tool.ts index 5c2e4d1..5af3b82 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/project/report-deprecated-css.tool.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/project/report-deprecated-css.tool.ts @@ -42,6 +42,12 @@ export const reportDeprecatedCssHandler = createHandler< async (params, { cwd, deprecatedCssClassesPath }) => { const { directory, componentName } = params; + if (!deprecatedCssClassesPath) { + throw new Error( + 'Missing ds.deprecatedCssClassesPath. Provide --ds.deprecatedCssClassesPath in mcp.json file.', + ); + } + const deprecated = getDeprecatedCssClasses( componentName, deprecatedCssClassesPath, diff --git a/packages/angular-mcp-server/src/lib/tools/ds/report-violations/report-all-violations.tool.ts b/packages/angular-mcp-server/src/lib/tools/ds/report-violations/report-all-violations.tool.ts index a624d56..b57a622 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/report-violations/report-all-violations.tool.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/report-violations/report-all-violations.tool.ts @@ -47,6 +47,11 @@ export const reportAllViolationsHandler = createHandler< >( reportAllViolationsSchema.name, async (params, { cwd, deprecatedCssClassesPath }) => { + if (!deprecatedCssClassesPath) { + throw new Error( + 'Missing ds.deprecatedCssClassesPath. Provide --ds.deprecatedCssClassesPath in mcp.json file.', + ); + } const groupBy = params.groupBy || 'file'; const dsComponents = await loadAndValidateDsComponentsFile( cwd, diff --git a/packages/angular-mcp-server/src/lib/tools/ds/shared/utils/handler-helpers.ts b/packages/angular-mcp-server/src/lib/tools/ds/shared/utils/handler-helpers.ts index 76a6e44..a446a17 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/shared/utils/handler-helpers.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/shared/utils/handler-helpers.ts @@ -26,8 +26,8 @@ export interface BaseHandlerOptions { export interface HandlerContext { cwd: string; workspaceRoot: string; - storybookDocsRoot: string; - deprecatedCssClassesPath: string; + storybookDocsRoot?: string; + deprecatedCssClassesPath?: string; uiRoot: string; } @@ -60,8 +60,8 @@ export function setupHandlerEnvironment( return { cwd, workspaceRoot: params.workspaceRoot || cwd, - storybookDocsRoot: params.storybookDocsRoot || '', - deprecatedCssClassesPath: params.deprecatedCssClassesPath || '', + storybookDocsRoot: params.storybookDocsRoot, + deprecatedCssClassesPath: params.deprecatedCssClassesPath, uiRoot: params.uiRoot || '', }; } diff --git a/packages/angular-mcp-server/src/lib/tools/ds/shared/violation-analysis/base-analyzer.ts b/packages/angular-mcp-server/src/lib/tools/ds/shared/violation-analysis/base-analyzer.ts index 302021b..11df2fa 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/shared/violation-analysis/base-analyzer.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/shared/violation-analysis/base-analyzer.ts @@ -29,6 +29,12 @@ export async function analyzeViolationsBase( process.chdir(cwd); + if (!deprecatedCssClassesPath) { + throw new Error( + 'Missing ds.deprecatedCssClassesPath. Provide --ds.deprecatedCssClassesPath in mcp.json file.', + ); + } + const deprecatedCssClasses = getDeprecatedCssClasses( componentName, deprecatedCssClassesPath || '', diff --git a/packages/angular-mcp-server/src/lib/validation/angular-mcp-server-options.schema.ts b/packages/angular-mcp-server/src/lib/validation/angular-mcp-server-options.schema.ts index 4237a03..acbfd04 100644 --- a/packages/angular-mcp-server/src/lib/validation/angular-mcp-server-options.schema.ts +++ b/packages/angular-mcp-server/src/lib/validation/angular-mcp-server-options.schema.ts @@ -10,14 +10,20 @@ export const AngularMcpServerOptionsSchema = z.object({ 'workspaceRoot must be an absolute path to the repository root where MCP server is working (e.g., /path/to/workspace-root)', }), ds: z.object({ - storybookDocsRoot: z.string().refine(isRelativePath, { - message: - 'ds.storybookDocsRoot must be a relative path from workspace root to the storybook project root (e.g., path/to/storybook/components)', - }), - deprecatedCssClassesPath: z.string().refine(isRelativePath, { - message: - 'ds.deprecatedCssClassesPath must be a relative path from workspace root to the file component to deprecated css classes mapping (e.g., path/to/components-config.js)', - }), + storybookDocsRoot: z + .string() + .optional() + .refine((val) => val === undefined || isRelativePath(val), { + message: + 'ds.storybookDocsRoot must be a relative path from workspace root to the storybook project root (e.g., path/to/storybook/components)', + }), + deprecatedCssClassesPath: z + .string() + .optional() + .refine((val) => val === undefined || isRelativePath(val), { + message: + 'ds.deprecatedCssClassesPath must be a relative path from workspace root to the file component to deprecated css classes mapping (e.g., path/to/components-config.js)', + }), uiRoot: z.string().refine(isRelativePath, { message: 'ds.uiRoot must be a relative path from workspace root to the components folder (e.g., path/to/components)', diff --git a/packages/angular-mcp-server/src/lib/validation/ds-components-file.validation.ts b/packages/angular-mcp-server/src/lib/validation/ds-components-file.validation.ts index c8012e3..45bd857 100644 --- a/packages/angular-mcp-server/src/lib/validation/ds-components-file.validation.ts +++ b/packages/angular-mcp-server/src/lib/validation/ds-components-file.validation.ts @@ -6,9 +6,14 @@ import { DsComponentsArraySchema } from './ds-components.schema'; export async function validateDeprecatedCssClassesFile( config: AngularMcpServerOptions, ): Promise { + const relPath = config.ds.deprecatedCssClassesPath; + if (!relPath) { + // Optional parameter not provided; nothing to validate + return; + } const deprecatedCssClassesAbsPath = path.resolve( config.workspaceRoot, - config.ds.deprecatedCssClassesPath, + relPath, ); let dsComponents; diff --git a/packages/angular-mcp-server/src/lib/validation/file-existence.ts b/packages/angular-mcp-server/src/lib/validation/file-existence.ts index f2ecdf6..6ad8253 100644 --- a/packages/angular-mcp-server/src/lib/validation/file-existence.ts +++ b/packages/angular-mcp-server/src/lib/validation/file-existence.ts @@ -13,14 +13,19 @@ export function validateAngularMcpServerFilesExist( const missingFiles: string[] = []; + // Always require uiRoot, optional: storybookDocsRoot, deprecatedCssClassesPath const dsPaths = [ - { label: 'ds.storybookDocsRoot', relPath: config.ds.storybookDocsRoot }, - { - label: 'ds.deprecatedCssClassesPath', - relPath: config.ds.deprecatedCssClassesPath, - }, + config.ds.storybookDocsRoot + ? { label: 'ds.storybookDocsRoot', relPath: config.ds.storybookDocsRoot } + : null, + config.ds.deprecatedCssClassesPath + ? { + label: 'ds.deprecatedCssClassesPath', + relPath: config.ds.deprecatedCssClassesPath, + } + : null, { label: 'ds.uiRoot', relPath: config.ds.uiRoot }, - ]; + ].filter(Boolean) as { label: string; relPath: string }[]; for (const { label, relPath } of dsPaths) { const absPath = path.resolve(root, relPath); diff --git a/packages/angular-mcp/src/main.ts b/packages/angular-mcp/src/main.ts index aeef3f1..07a9065 100644 --- a/packages/angular-mcp/src/main.ts +++ b/packages/angular-mcp/src/main.ts @@ -60,8 +60,8 @@ const argv = yargs(hideBin(process.argv)) const { workspaceRoot, ds } = argv as unknown as { workspaceRoot: string; ds: { - storybookDocsRoot: string; - deprecatedCssClassesPath: string; + storybookDocsRoot?: string; + deprecatedCssClassesPath?: string; uiRoot: string; }; };