rules
Manage rule profiles in projects by adding or removing specific configurations like Cursor, Claude, or VSCode. Ensure precise control over project settings through defined actions and profiles.
Instructions
Add or remove rule profiles from the project.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | Whether to add or remove rule profiles. | |
| force | No | DANGEROUS: Force removal even if it would leave no rule profiles. Only use if you are absolutely certain. | |
| profiles | Yes | List of rule profiles to add or remove (e.g., ["cursor", "roo"]). Available options: amp, claude, cline, codex, cursor, gemini, kiro, opencode, roo, trae, vscode, windsurf, zed | |
| projectRoot | Yes | The root directory of the project. Must be an absolute path. |
Implementation Reference
- mcp-server/src/tools/rules.js:19-63 (registration)Registers the MCP 'rules' tool with server.addTool, defining name, description, parameters schema, and execute handler that delegates to rulesDirect.export function registerRulesTool(server) { server.addTool({ name: 'rules', description: 'Add or remove rule profiles from the project.', parameters: z.object({ action: z .enum(['add', 'remove']) .describe('Whether to add or remove rule profiles.'), profiles: z .array(z.enum(RULE_PROFILES)) .min(1) .describe( `List of rule profiles to add or remove (e.g., [\"cursor\", \"roo\"]). Available options: ${RULE_PROFILES.join(', ')}` ), projectRoot: z .string() .describe( 'The root directory of the project. Must be an absolute path.' ), force: z .boolean() .optional() .default(false) .describe( 'DANGEROUS: Force removal even if it would leave no rule profiles. Only use if you are absolutely certain.' ) }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info( `[rules tool] Executing action: ${args.action} for profiles: ${args.profiles.join(', ')} in ${args.projectRoot}` ); const result = await rulesDirect(args, log, { session }); return handleApiResult({ result, log, projectRoot: args.projectRoot }); } catch (error) { log.error(`[rules tool] Error: ${error.message}`); return createErrorResponse(error.message, { details: error.stack }); } }) }); }
- Core handler implementation for the rules tool. Handles 'add' and 'remove' actions on rule profiles, performs safety checks, file conversions/removals, and returns structured results.export async function rulesDirect(args, log, context = {}) { enableSilentMode(); try { const { action, profiles, projectRoot, yes, force } = args; if ( !action || !Array.isArray(profiles) || profiles.length === 0 || !projectRoot ) { return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'action, profiles, and projectRoot are required.' } }; } const removalResults = []; const addResults = []; if (action === RULES_ACTIONS.REMOVE) { // Safety check: Ensure this won't remove all rule profiles (unless forced) if (!force && wouldRemovalLeaveNoProfiles(projectRoot, profiles)) { const installedProfiles = getInstalledProfiles(projectRoot); const remainingProfiles = installedProfiles.filter( (profile) => !profiles.includes(profile) ); return { success: false, error: { code: 'CRITICAL_REMOVAL_BLOCKED', message: `CRITICAL: This operation would remove ALL remaining rule profiles (${profiles.join(', ')}), leaving your project with no rules configurations. This could significantly impact functionality. Currently installed profiles: ${installedProfiles.join(', ')}. If you're certain you want to proceed, set force: true or use the CLI with --force flag.` } }; } for (const profile of profiles) { if (!isValidProfile(profile)) { removalResults.push({ profileName: profile, success: false, error: `The requested rule profile for '${profile}' is unavailable. Supported profiles are: ${RULE_PROFILES.join(', ')}.` }); continue; } const profileConfig = getRulesProfile(profile); const result = removeProfileRules(projectRoot, profileConfig); removalResults.push(result); } const successes = removalResults .filter((r) => r.success) .map((r) => r.profileName); const skipped = removalResults .filter((r) => r.skipped) .map((r) => r.profileName); const errors = removalResults.filter( (r) => r.error && !r.success && !r.skipped ); const withNotices = removalResults.filter((r) => r.notice); let summary = ''; if (successes.length > 0) { summary += `Successfully removed Task Master rules: ${successes.join(', ')}.`; } if (skipped.length > 0) { summary += `Skipped (default or protected): ${skipped.join(', ')}.`; } if (errors.length > 0) { summary += errors .map((r) => `Error removing ${r.profileName}: ${r.error}`) .join(' '); } if (withNotices.length > 0) { summary += ` Notices: ${withNotices.map((r) => `${r.profileName} - ${r.notice}`).join('; ')}.`; } disableSilentMode(); return { success: errors.length === 0, data: { summary, results: removalResults } }; } else if (action === RULES_ACTIONS.ADD) { for (const profile of profiles) { if (!isValidProfile(profile)) { addResults.push({ profileName: profile, success: false, error: `Profile not found: static import missing for '${profile}'. Valid profiles: ${RULE_PROFILES.join(', ')}` }); continue; } const profileConfig = getRulesProfile(profile); const { success, failed } = convertAllRulesToProfileRules( projectRoot, profileConfig ); // Determine paths const rulesDir = profileConfig.rulesDir; const profileRulesDir = path.join(projectRoot, rulesDir); const profileDir = profileConfig.profileDir; const mcpConfig = profileConfig.mcpConfig !== false; const mcpPath = mcpConfig && profileConfig.mcpConfigPath ? path.join(projectRoot, profileConfig.mcpConfigPath) : null; // Check what was created const mcpConfigCreated = mcpConfig && mcpPath ? fs.existsSync(mcpPath) : undefined; const rulesDirCreated = fs.existsSync(profileRulesDir); const profileFolderCreated = fs.existsSync( path.join(projectRoot, profileDir) ); const error = failed > 0 ? `${failed} rule files failed to convert.` : null; const resultObj = { profileName: profile, mcpConfigCreated, rulesDirCreated, profileFolderCreated, skipped: false, error, success: (mcpConfig ? mcpConfigCreated : true) && rulesDirCreated && success > 0 && !error }; addResults.push(resultObj); } const successes = addResults .filter((r) => r.success) .map((r) => r.profileName); const errors = addResults.filter((r) => r.error && !r.success); let summary = ''; if (successes.length > 0) { summary += `Successfully added rules: ${successes.join(', ')}.`; } if (errors.length > 0) { summary += errors .map((r) => ` Error adding ${r.profileName}: ${r.error}`) .join(' '); } disableSilentMode(); return { success: errors.length === 0, data: { summary, results: addResults } }; } else { disableSilentMode(); return { success: false, error: { code: 'INVALID_ACTION', message: `Unknown action. Use "${RULES_ACTIONS.ADD}" or "${RULES_ACTIONS.REMOVE}".` } }; } } catch (error) { disableSilentMode(); log.error(`[rulesDirect] Error: ${error.message}`); return { success: false, error: { code: error.code || 'RULES_ERROR', message: error.message } }; } }
- mcp-server/src/tools/tool-registry.js:62-62 (registration)toolRegistry entry that maps 'rules' tool name to its registration function registerRulesTool, used for dynamic tool registration.rules: registerRulesTool,
- mcp-server/src/tools/rules.js:23-45 (schema)Zod input schema for the rules tool parameters.parameters: z.object({ action: z .enum(['add', 'remove']) .describe('Whether to add or remove rule profiles.'), profiles: z .array(z.enum(RULE_PROFILES)) .min(1) .describe( `List of rule profiles to add or remove (e.g., [\"cursor\", \"roo\"]). Available options: ${RULE_PROFILES.join(', ')}` ), projectRoot: z .string() .describe( 'The root directory of the project. Must be an absolute path.' ), force: z .boolean() .optional() .default(false) .describe( 'DANGEROUS: Force removal even if it would leave no rule profiles. Only use if you are absolutely certain.' ) }),
- src/constants/rules-actions.js:8-11 (helper)Constants for rules actions used in the handler logic.export const RULES_ACTIONS = { ADD: 'add', REMOVE: 'remove' };