Cursor MDC Rules: The Complete Technical Guide

.cursorrules is deprecated. Cursor now uses .mdc files in .cursor/rules/ with YAML frontmatter that controls exactly when and how rules inject into the LLM context. This guide covers the format spec, four rule types, glob scoping, context budget, and real examples.

March 3, 2026 · 1 min read

TL;DR: The Migration in 30 Seconds

The .cursorrules file at your project root is deprecated. Cursor 0.45 introduced .cursor/rules/, a directory of .mdc files that replace it. Each MDC file has YAML frontmatter controlling when it activates. When both exist in a project, the MDC files win. In agent mode specifically, .cursorrules shows 0% compliance in community testing. MDC with alwaysApply: true shows 100%.

Old: .cursorrules

Single file at project root. All rules injected into every context. No scoping. Deprecated in Cursor 0.45. Ignored in agent mode when MDC files exist.

New: .cursor/rules/*.mdc

Multiple files with YAML frontmatter. Four activation modes. Glob-based scoping to file types. Full agent mode support. Version-controlled per project.

Migration

Create .cursor/rules/. Split .cursorrules by topic. Add frontmatter to each file. Set alwaysApply: true for global rules. Globs for file-specific rules.

The MDC Format: Full Specification

MDC stands for Markdown Configuration (sometimes called Markdown with Directives). The format is standard Markdown with a YAML frontmatter block at the top. The .mdc extension is what triggers Cursor to parse the frontmatter; plain .md files in .cursor/rules/ work but Cursor treats them as manual-only rules.

Complete MDC file structure

---
description: "TypeScript coding standards for this project"
globs: ["src/**/*.ts", "src/**/*.tsx"]
alwaysApply: false
---

# TypeScript Standards

Use strict TypeScript. No implicit any.

- Prefer interfaces over type aliases for object shapes
- Use const assertions for literal types
- All async functions must have return type annotations
- Error types must extend Error, not be plain strings

## Naming Conventions

- Components: PascalCase
- Hooks: camelCase with use prefix
- Utilities: camelCase
- Constants: SCREAMING_SNAKE_CASE

Frontmatter Fields

FieldTypePurposeRequired?
descriptionstringShown in Cursor UI; used by agent for intelligent selectionFor agent-requested rules
globsstring[]File patterns that trigger auto-attach; must be YAML arrayFor auto-attach rules
alwaysApplybooleanWhen true: included in every chat session regardless of globsNo (default: false)

The combination of these three fields determines which of the four rule types your file becomes. There is no explicit type field. The type is inferred from what you set.

Minimal valid MDC file (agent-requested type)

---
description: "Git commit message conventions: use conventional commits format with type(scope): description"
alwaysApply: false
---

All commits must follow Conventional Commits:

feat(auth): add OAuth2 login
fix(api): handle null response from payment service
docs(readme): update installation steps

Types: feat, fix, docs, style, refactor, test, chore

How Rules Inject Into Context

When Cursor assembles a prompt to send to the model, it inserts active rules at the start of the model context, before the conversation history and before any attached files. The model sees rules as part of its system-level instructions.

The injection pipeline works like this:

  1. Team Rules are loaded first (organization-wide, managed via dashboard)
  2. User Rules from Cursor Settings are added next
  3. Project Rules are evaluated: always-apply files are added unconditionally; glob-match files are checked against the current file; agent-requested files are made available to the model as readable resources
  4. The assembled rule content is prepended to the system prompt
  5. Conversation history, attached files, and codebase context follow

Why Position Matters

Rules at the start of context are weighted more heavily by most LLMs than instructions buried in the middle. This is the "lost in the middle" effect: models trained on next-token prediction tend to prioritize tokens near the beginning and end of long contexts. Keeping rules concise and front-loaded means they actually influence output.

Cursor Tab vs Chat vs Agent

The rule system applies differently across Cursor's three interaction modes:

ModeUses Rules?Notes
Cursor Tab (autocomplete)NoUses a separate, lean completion pipeline optimized for latency. Rules are not injected.
Cmd+K (inline edit)PartialInline edits use a simpler context. User rules from Settings apply; project MDC rules may not.
Chat modeYesFull rule injection. Always-apply, auto-attach, and agent-requested rules all work here.
Agent modeYes (MDC only)Same as Chat, but .cursorrules is ignored if any .mdc files exist. Use MDC with alwaysApply: true.

This means rules you write mainly affect Chat and Agent workflows. Code style conventions in your MDC rules will not affect every keystroke autocomplete. If you want consistent naming enforced at the autocomplete level, that requires separate configuration.

Four Rule Types Explained

Cursor's rule system has four activation modes. The type is determined entirely by your frontmatter combination. There is no explicit type field.

Rule TypeFrontmatter ConfigWhen ActiveBest For
Always ApplyalwaysApply: trueEvery chat session, no exceptionsGlobal coding standards, project architecture rules
Auto Attachglobs defined, alwaysApply: falseWhen current file matches a glob patternLanguage-specific rules, framework conventions
Agent Requesteddescription only, no globs, alwaysApply: falseAgent reads description and includes if relevantReference docs, API patterns, specialized guides
ManualNo description, no globs, no alwaysApplyOnly when you type @rule-name in chatRarely-used guides, one-off templates

Always Apply

Always Apply rule (.cursor/rules/core-standards.mdc)

---
description: "Core project standards applied to every session"
alwaysApply: true
---

## Project: Payments API

Stack: TypeScript, Express, PostgreSQL, Drizzle ORM

Rules:
- Use Drizzle for all database queries. No raw SQL.
- All API endpoints must validate input with Zod schemas
- Error responses use { error: string, code: string } format
- Never log sensitive data (PII, payment info, tokens)

Auto Attach

Auto Attach rule (.cursor/rules/react-components.mdc)

---
description: "React component conventions for this project"
globs: ["src/components/**/*.tsx", "src/app/**/*.tsx"]
alwaysApply: false
---

## React Component Rules

- Functional components only. No class components.
- Props interface named ComponentNameProps
- Use React.FC<Props> type annotation
- Co-locate component tests in __tests__/ subdirectory
- Use Tailwind for all styling. No CSS modules or styled-components.
- Custom hooks extracted to src/hooks/ when used by 2+ components

Agent Requested

Agent-requested rules are a reference system. The agent reads all descriptions at the start of a session and decides which rules to fetch and read in full. A good description is key: vague descriptions get skipped.

Agent Requested rule (.cursor/rules/stripe-integration.mdc)

---
description: "Stripe payment integration patterns: webhook handling, idempotency keys, subscription lifecycle"
alwaysApply: false
---

## Stripe Integration Patterns

### Webhook Handling
Always verify webhook signatures before processing.
Use idempotency keys for all payment operations.

```typescript
const sig = request.headers['stripe-signature'];
stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET);
```

### Subscription States
Track: active, past_due, canceled, trialing
Handle payment_intent.payment_failed with retry logic.

Manual

Manual rule (.cursor/rules/migration-template.mdc)

---
description: ""
globs: []
alwaysApply: false
---

## Database Migration Template

Use this template when writing Drizzle migrations.
Add a rollback section for every destructive change.

```typescript
// Migration: YYYYMMDD_description
export async function up(db: PostgresJsDatabase) {
  // Forward migration
}

export async function down(db: PostgresJsDatabase) {
  // Rollback
}
```

Invoke a manual rule with @migration-template in chat. The rule name matches the filename without the .mdc extension.

.cursorrules vs .mdc Migration

Aspect.cursorrules.mdc files
LocationProject root (single file).cursor/rules/ (directory of files)
StatusDeprecated (Cursor 0.45+)Current standard
ScopingNone (always injected)Per-file via globs, or by activation mode
Agent modeIgnored when MDC files existFull support
Multiple rule setsNo (one blob)Yes (one file per concern)
Version controlYes (single file)Yes (directory tracked by git)
Team sharingYes (via git)Yes + Team Rules dashboard
Token costFixed: all rules every requestVariable: only active rules injected

Step-by-Step Migration

Step 1: Create the directory

mkdir -p .cursor/rules

Step 2: Split your .cursorrules by concern

# Example: a monolithic .cursorrules becomes:
# .cursor/rules/001-core.mdc          → always-apply, global project rules
# .cursor/rules/100-typescript.mdc    → auto-attach, *.ts and *.tsx files
# .cursor/rules/101-python.mdc        → auto-attach, *.py files
# .cursor/rules/200-testing.mdc       → auto-attach, *.test.* and *.spec.*
# .cursor/rules/300-git-commits.mdc   → agent-requested, commit conventions

Step 3: Example converted core rule

---
description: "Core project conventions, always active"
alwaysApply: true
---

## Project: [Your Project Name]

Stack: [your stack here]
Package manager: [bun/npm/pnpm]

## Core Rules

[paste the global section of your .cursorrules here]

Step 4: Verify MDC files are detected

In Cursor Settings > Rules, you should see your new .mdc files listed.
Any file showing "Auto" in the activation column is working correctly.
Files showing "Manual" only appear when explicitly @-mentioned.

Leave .cursorrules in place during transition

Once you have MDC files in .cursor/rules/, Cursor gives them precedence over .cursorrules. You can delete the old file or keep it for backward compatibility with teammates who haven't updated Cursor. In agent mode, MDC files always win regardless.

Why .cursorrules Breaks in Agent Mode

This is one of the most common confusing behaviors in Cursor. You have rules in .cursorrules, the agent ignores them completely.

The Technical Reason

Cursor's agent mode uses a different context assembly pipeline than the older Chat mode that .cursorrules was designed for. When the agent initializes, it scans for MDC files in .cursor/rules/ first. If any MDC files are found, the old .cursorrules file is skipped entirely. The MDC system was designed to replace .cursorrules, so the agent mode was built around MDC from the start.

Community testing (documented in the Cursor forum, March 2025) ran 9 compliance checks against identical rules. Results:

.cursorrules in Agent Mode

0 out of 9 compliance checks passed. Even with only a .cursorrules file and no MDC files present, agent mode showed inconsistent or zero compliance in many test environments.

.mdc with alwaysApply: true

9 out of 9 compliance checks passed. Same rules, different format. The MDC file with alwaysApply: true achieved full compliance across all test runs.

The alwaysApply: true Requirement

Even with MDC files, rules without alwaysApply: true showed 0 out of 3 compliance in agent mode tests. The agent needs explicit always-apply rules to guarantee injection. Agent-requested rules (description only) are available but the agent may not fetch them for every task.

Rule for agent mode

If you need Cursor's agent to reliably follow a rule, put it in a .mdc file with alwaysApply: true. Anything else is best-effort. The agent may read agent-requested rules when it deems them relevant, but "always" rules are the only guarantee.

How Auto Model Compounds the Problem

If you have the agent model set to "Auto," Cursor selects between models dynamically. Some reported behavior shows that with Auto model selected, even MDC rules can fail to apply, and sub-agent calls may not work. The most reliable configuration is a specific model (Claude Sonnet, Claude Opus, or similar) combined with MDC files and explicit alwaysApply: true.

Glob Patterns: Syntax and Gotchas

Glob patterns in MDC frontmatter must be a YAML array. This is the most common mistake. A comma-separated string looks valid but silently ignores everything after the first comma.

Correct vs incorrect glob syntax

# CORRECT: YAML array format
globs: ["src/**/*.ts", "src/**/*.tsx"]

# ALSO CORRECT: multi-line array
globs:
  - "src/**/*.ts"
  - "src/**/*.tsx"
  - "!src/**/*.test.ts"

# WRONG: comma-separated string (second pattern is silently ignored)
globs: "src/**/*.ts, src/**/*.tsx"

Pattern Reference

PatternMatchesUse Case
**/*.tsAll TypeScript files recursivelyTypeScript-wide rules
src/components/**/*.tsxTSX files under src/components/React component rules
**/*.test.{ts,js}Test files in any languageTesting conventions
**/*.{py,pyi}Python source and stub filesPython project rules
src/api/**/*.tsTypeScript files under src/api/API endpoint rules
!**/__mocks__/**Exclude mock directories (negation)Combine with include patterns
**/*.{js,jsx,ts,tsx}All JavaScript and TypeScript filesUniversal JS/TS rules
*.config.{js,ts}Config files at project rootBuild tool conventions

Single vs Double Asterisk

src/* matches files one level deep: src/index.ts matches, src/utils/helpers.ts does not.

src/** matches recursively: src/index.ts and src/utils/helpers.ts both match.

For most rules targeting a directory tree, you want double asterisk.

Rule Examples by Project Type

TypeScript Node.js API

.cursor/rules/001-core.mdc

---
description: "Core conventions for TypeScript API project"
alwaysApply: true
---

Stack: TypeScript, Node.js, Express, PostgreSQL, Drizzle ORM, Zod
Package manager: pnpm

## API Design
- All routes in src/routes/
- Route handlers are thin: validate input, call service, return response
- Services in src/services/ handle business logic
- Database queries only in src/db/ via Drizzle

## Error Handling
- All errors extend AppError class from src/errors.ts
- HTTP errors: { error: string; code: string; status: number }
- Log with structured logger (src/logger.ts), never console.log
- Never expose stack traces in API responses

.cursor/rules/100-typescript.mdc

---
description: "TypeScript strict mode conventions"
globs: ["**/*.ts", "**/*.tsx"]
alwaysApply: false
---

- Strict mode enabled. No implicit any.
- Return types required on all exported functions
- Prefer type inference for local variables
- Use unknown over any for external data; narrow before use
- Enums: prefer string literal unions over enum keyword
- Async: all async functions return Promise<T> with explicit T

Python Monorepo

.cursor/rules/100-python.mdc

---
description: "Python coding standards"
globs: ["**/*.py", "**/*.pyi"]
alwaysApply: false
---

- Python 3.11+. Use type hints on all function signatures.
- Black formatter (88 char line length), isort for imports
- Pydantic v2 for data validation. Not dataclasses for API models.
- Prefer pathlib.Path over os.path
- Async: use asyncio and httpx, not requests
- Tests: pytest with pytest-asyncio, fixtures in conftest.py

## Import Order (isort config)
stdlib -> third-party -> first-party (src/) -> relative

.cursor/rules/200-testing.mdc

---
description: "Testing conventions for Python and pytest"
globs: ["**/test_*.py", "**/*_test.py", "**/conftest.py"]
alwaysApply: false
---

## Test Structure
- Test files mirror src/ structure under tests/
- One test class per module under test
- Fixture scope: function by default, session for expensive fixtures

## What to Test
- Happy path + at least two error cases per function
- External calls (HTTP, DB) must be mocked
- No real network calls in unit tests

## Naming
- test_does_thing_when_condition: descriptive, no abbreviations

React Frontend

.cursor/rules/100-components.mdc

---
description: "React component patterns and Tailwind conventions"
globs: ["src/components/**/*.tsx", "src/app/**/*.tsx", "src/pages/**/*.tsx"]
alwaysApply: false
---

## Components
- Functional components with TypeScript. No class components.
- Props: interface ComponentNameProps above the component
- Default export for page components, named export for shared components
- Co-locate stories and tests with components

## State
- useState for local UI state
- Zustand for cross-component state (src/store/)
- TanStack Query for server state

## Styling
- Tailwind CSS. No inline styles. No CSS modules.
- Use cn() utility (clsx + tailwind-merge) for conditional classes
- Responsive: mobile-first with sm:, md:, lg: prefixes

Git Commit Conventions

.cursor/rules/300-commits.mdc

---
description: "Git commit message format: conventional commits with scope"
alwaysApply: false
---

## Commit Format

type(scope): short description

Body (optional): why, not what

## Types
feat: new feature
fix: bug fix
refactor: no behavior change
docs: documentation only
test: tests only
chore: tooling, dependencies

## Scopes
api, auth, db, ui, payments, infra

## Rules
- Subject line under 72 chars
- Imperative mood: "add" not "added"
- Reference issues: feat(auth): add OAuth2 login (#142)

Context Budget and Token Math

Every always-apply rule is included in every request. The token cost compounds across rules. Most Cursor models run on 200k token context windows, so rules themselves are rarely the bottleneck, but they do have measurable cost.

Rough Token Estimates

Rule File SizeApprox. TokensCost per Session
50-line rule file~100-200 tokensNegligible: less than 0.1% of 200k window
100-line rule file~200-400 tokensSmall: 5 such files = 2,000 tokens overhead
500-line rule file~1,000-2,000 tokensNoticeable if alwaysApply: true
20 always-apply rules @ 200 tokens each~4,000 tokens2% of context consumed before any code is attached

Where Context Actually Goes

In a typical agent session, the context budget breaks down roughly as:

  • Always-apply rules: 500-5,000 tokens depending on how many you have
  • Auto-attached rules (current file type): 200-1,000 tokens
  • Conversation history: grows with session length
  • Attached files and codebase context: typically 10,000-50,000+ tokens for a real project
  • Model response: reserved output tokens

Rules are not the expensive part. File context is. A single large source file attached with @ dwarfs even a verbose rule set. The reason to keep rules concise is quality, not context capacity. Long, wordy rules get harder for the model to apply consistently. The "lost in the middle" effect means instructions buried in a 500-line rule file are followed less reliably than instructions in a focused 50-line file.

Practical Guidelines

  • Keep individual rule files under 100 lines
  • Limit always-apply rules to genuine universals (project stack, core error handling, security constraints)
  • Move language or framework specifics to auto-attach rules scoped by glob
  • Use agent-requested rules for reference material (API patterns, schema docs, integration guides) that only matters sometimes
  • Delete rules the model already follows without them. If the AI gets your naming convention right without a rule, the rule is wasting tokens

Common Mistakes

Mistake 1: Comma-separated globs

globs: "**/*.ts, **/*.tsx" silently ignores everything after the first pattern. Use a YAML array: globs: ["**/*.ts", "**/*.tsx"].

Mistake 2: Using .cursorrules with agent mode

If you have any MDC files in .cursor/rules/, the .cursorrules file is ignored in agent mode. The fix: migrate all rules to MDC. Set alwaysApply: true on rules that were global in .cursorrules.

Mistake 3: Agent-requested rules with vague descriptions

The agent reads your description to decide whether to fetch a rule. "Project rules" tells the agent nothing. "Stripe webhook handling, idempotency keys, payment error recovery patterns" tells it exactly when to pull the rule in.

Mistake 4: Saving rule files in Cursor

There is a known bug where changes to .mdc files sometimes do not save to disk. Workaround: close Cursor completely, choose "Override" on the unsaved changes dialog, then reopen. Verify the file contents on disk with a terminal editor after saving.

Mistake 5: Single massive rule file

Putting everything in one always-apply rule file is essentially rebuilding .cursorrules. The point of MDC is scoping. A 400-line always-apply rule has the same token cost as the old system. Split by concern, scope by glob, and keep each file under 100 lines.

Mistake 6: Agent model set to Auto

The "Auto" model selection in agent mode can cause rules to not apply and sub-agent calls to fail. For reliable rule enforcement, select a specific model. Claude Sonnet or Claude Opus in the model dropdown, combined with MDC files and alwaysApply: true, gives the most consistent results.

Frequently Asked Questions

What is the difference between .cursorrules and .mdc files?

.cursorrules is a single file at the project root that injects all rules into every context. Deprecated since Cursor 0.45. .mdc files live in .cursor/rules/ and use YAML frontmatter to control when each rule activates. You can scope rules to specific file types, make them always-on, or let the agent decide relevance.

Why are my Cursor rules not working in agent mode?

Two likely causes. First: you have .mdc files in .cursor/rules/ and a .cursorrules file. MDC takes precedence; .cursorrules is ignored. Second: your MDC files lack alwaysApply: true. Agent-requested rules (description only) may not be fetched for every task.

What are the four Cursor rule types?

Always Apply (alwaysApply: true): every session. Auto Attach (globs defined): triggers on matching files. Agent Requested (description only): agent decides when to include. Manual (no frontmatter fields set): only via @rule-name in chat.

How many tokens do Cursor rules use?

Roughly 2-4 tokens per word. A 100-line rule file is 200-500 tokens. Five always-apply rules at 300 tokens each cost 1,500 tokens per request before any code is attached. Cursor models have 200k token windows, so rules alone are rarely the constraint. The real cost is in consistency: longer rules are applied less reliably due to the lost-in-the-middle effect.

What is the correct glob syntax for MDC rules?

YAML array, not comma-separated string. Correct: globs: ["src/**/*.ts", "**/*.tsx"]. Wrong: globs: "src/**/*.ts, **/*.tsx" (silently drops the second pattern). Use double asterisk for recursive matching: src/**/*.ts matches all nested TypeScript files under src/.

How do I migrate from .cursorrules to MDC?

Create .cursor/rules/. Split your .cursorrules content by concern into separate files (core, typescript, testing, commits, etc.). Add YAML frontmatter: alwaysApply: true for global rules, globs for file-specific rules. MDC files take precedence automatically once they exist.

What is the rule precedence order?

Team Rules (dashboard-managed, org-wide) take the highest precedence, then Project Rules (.cursor/rules/*.mdc), then User Rules (Cursor Settings). When rule files within a tier conflict, later-loaded files win. Numeric prefixes on filenames (001-core.mdc, 100-typescript.mdc) give you control over load order.

Do rules affect Cursor Tab autocomplete?

No. Cursor Tab uses a separate, latency-optimized completion pipeline that does not inject MDC rules. Rules affect Chat and Agent mode. Cmd+K (inline edit) uses a partial context; user-level rules from Cursor Settings may apply, but project MDC rules may not. For consistent behavior, use Agent mode for rule-dependent tasks.

Related Pages

Better Codebase Context for Cursor and Claude Code

WarpGrep is an agentic code search MCP server. It gives Cursor and Claude Code deep semantic search over your codebase so rules about architecture and patterns are grounded in actual code.

Sources