SSD Nodes Learn 🎉 VPS from $4.99/mo
Guides Matt ConnorBy Matt Connor

Agent skills vs MCP servers vs rules files

Three ways to give a coding agent context, and one rule for picking. See what a skill, an MCP server and a rules file each cost in tokens and upkeep.

Agent skills vs MCP servers vs rules files: the short answer

Agent skills, MCP servers and rules files all put knowledge in front of a coding agent. Pick by what the knowledge does. MCP (model context protocol) is for data that can be different the next time you look at it. A skill is for a procedure you could write down today and still be right in six weeks. A rules file is for the few facts that must hold in every session.

That choice has a price, and the price is context. Every token spent on an instruction the agent did not need is a token unavailable for the code it is reading. It is also a token you pay again on every turn, because the whole context window is resent with each request. So the useful question is not which mechanism can do the job. Most days all three can. The question is which one costs the least while it sits idle.

What each one costs before you use it

The three load at different moments, and that timing is the whole difference.

A rules file loads in full at launch, every session, relevant or not. Claude Code reads CLAUDE.md at the start of every conversation and loads it in full regardless of length. The documented target is under 200 lines per file, because a longer file costs more context and gets followed less reliably. Those two effects push the same way, which is why a 900-line rules file is worse than useless.

A skill loads in two stages. At startup only the description line from each SKILL.md frontmatter enters context, so the model knows the skill exists and roughly when it applies. The body loads when the skill is invoked. A 400-line reference document therefore costs you almost nothing until the moment it is needed.

An MCP server used to be the expensive one, and this is where most comparisons you will read are now out of date. Tool search is on by default in current Claude Code. Only the tool names and the server's instructions field load at session start, and the full JSON (JavaScript object notation) schemas are deferred until Claude searches for them. Adding a server no longer costs thousands of tokens up front. It still costs something, and it still costs everything up front in the configurations where tool search is off.

ChartStartup and post-use context cost, estimated tokens
The data behind this chart
[
  {
    "label": "Rules file, 200 lines",
    "at_startup": "2,500",
    "after_use": "2,500"
  },
  {
    "label": "Skill, 12 KB body",
    "at_startup": 40,
    "after_use": "3,000"
  },
  {
    "label": "MCP server, tool search on",
    "at_startup": 500,
    "after_use": "3,200"
  },
  {
    "label": "MCP server, tool search off",
    "at_startup": "4,500",
    "after_use": "4,500"
  }
]

Those are estimates, not measurements from your machine. They come from the size of the text each mechanism loads, at roughly four characters per token: a 200-line rules file is about 10 KB of markdown, a skill description is about 160 characters, and a server exposing twelve tools carries about 18 KB of schema plus a 2 KB instructions block. Claude Code truncates each tool description and each server instructions field at 2 KB, so that part has a ceiling. The next section shows you how to read your own real numbers instead.

Read the first two rows together. The rules file costs 2,500 tokens in a session where nobody needed it. The skill costs 40 tokens in that same session, and 3,000 in the one session out of ten where it fires. The last two rows are the same server twice, with tool search on and off: 500 tokens against 4,500. That gap is the reason old advice about MCP context bloat still circulates.

Tool search needs a model that supports tool_reference blocks, which as of August 2026 means Claude Sonnet 4.5, Haiku 4.5, Opus 4.5 and later. Claude Code turns it off when ANTHROPIC_BASE_URL points at a host that is not first party, because most proxies do not forward those blocks. Set ENABLE_TOOL_SEARCH to control it: false loads every schema up front, true defers all of them, and auto loads them up front only when they fit inside 10% of the context window.

# Load schemas up front only if they fit in 5% of the window
ENABLE_TOOL_SEARCH=auto:5 claude

The deciding question: does the data change between invocations?

Ask that first, because it eliminates one option outright. If the agent needs to read or write something that can be different the next time it looks, you need a server. An issue tracker, a database, a monitoring dashboard, your own internal API (application programming interface). Writing it down does not help, because what you wrote is stale the moment somebody else edits the record.

If the answer would still be correct in six weeks with nobody maintaining it, you want a skill. A release checklist. A migration procedure. The shape of your error responses. How this repository wants tests written. A skill is a file in git. It has no port, no process, and no failure mode beyond being wrong, which a code review can catch.

If it is one fact that must apply to work you have not thought of yet, put it in the rules file. Run make lint before committing. Never push to main. Handlers live in src/api/handlers/. One line each. The moment an entry grows into steps, it has stopped being a fact and become a procedure, and it should move into a skill.

When a rules file is enough

Rules files load from several places, from broadest to most specific: a managed policy file, your personal ~/.claude/CLAUDE.md, the project's ./CLAUDE.md or ./.claude/CLAUDE.md, and a gitignored ./CLAUDE.local.md. All discovered files are concatenated rather than overriding each other, and files closer to your working directory are read last.

Claude Code reads CLAUDE.md, not AGENTS.md. If your repository already carries an AGENTS.md for other tools, do not maintain two copies that will drift apart.

ln -s AGENTS.md CLAUDE.md

The symlink prints nothing on success. Start a session, run /context, and confirm CLAUDE.md appears under Memory files. If it is not listed there, the agent has never seen it and no rewording will help. When you also want Claude-specific lines, use the import form instead and put them below the import.

@AGENTS.md

## Claude Code

Use plan mode for changes under `src/billing/`.

One trap sits here. @path imports do not save context. The imported file is expanded and loaded at launch alongside the file that referenced it, up to four hops deep. Splitting a 600-line rules file into six imports organises it for humans and changes the token cost by exactly nothing. The conventions behind AGENTS.md and its human-facing twin are worth reading before you settle on a layout.

What does reduce the cost is .claude/rules/ with a paths field. A rule file carrying paths frontmatter loads only when the agent touches a file matching one of the patterns.

---
paths:
  - "src/api/**/*.ts"
---

# API rules

- Every endpoint validates its input.
- Use the standard error response shape.

A rule with no paths field loads at launch with the same priority as .claude/CLAUDE.md. So the working pattern is short unconditional rules, plus a paths list on anything that only matters inside one directory.

When you want a skill

A skill is a directory with a SKILL.md inside it. Personal skills live at ~/.claude/skills/<name>/SKILL.md and apply to every project on your machine. Project skills live at .claude/skills/<name>/SKILL.md, travel with the repository, and can be reviewed in a pull request like any other file.

mkdir -p ~/.claude/skills/summarize-changes
---
name: summarize-changes
description: Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed, wants a commit message, or asks to review their diff.
---

Run `git status` and `git diff` against the merge base.
Group the changes by intent, not by file.
Call out anything touching auth, migrations or deletions.

The description is the only part of that file sitting in context before the skill runs, so it is doing two jobs. It says what the skill does, and it says when to reach for it. A description reading "Helps with deploys" gives the model nothing to match a request against, so the skill quietly never fires and you conclude skills do not work.

The directory name becomes the command, so the example above gives you /summarize-changes. In a personal or project skill the frontmatter name sets only the display label in listings.

Once a skill is invoked, its rendered content enters the conversation as a single message and stays there for the rest of the session. Claude Code does not re-read the file on later turns. Write standing instructions rather than one-time steps, and keep the body tight, because from that point every line is a recurring cost on each request. After auto-compaction, Claude Code re-attaches the most recent invocation of each skill, keeping the first 5,000 tokens of each inside a combined budget of 25,000 tokens. Invoke several large skills in one session and the oldest are dropped entirely, which is why a skill can seem to stop mattering after a long conversation. Invoke it again and it comes back. When the same procedure applies to more than one codebase, share one skill across several repositories rather than copying the file around.

When you need an MCP server

Adding one is a single command, and the transport decides its shape.

# Remote HTTP server
claude mcp add --transport http notion https://mcp.notion.com/mcp

# Remote HTTP server behind a bearer token
claude mcp add --transport http secure-api https://api.example.com/mcp \
  --header "Authorization: Bearer your-token"

# Local stdio server: everything after -- is passed through untouched
claude mcp add --env AIRTABLE_API_KEY=YOUR_KEY --transport stdio airtable \
  -- npx -y airtable-mcp-server

The -- matters. For a stdio server it separates Claude Code's own options from the command line that starts your server. Leave it out and a --port 8080 meant for the server is parsed as an option to claude mcp add, which then rejects it.

claude mcp list
claude mcp get notion

claude mcp add confirms with an Added ... line, which only tells you the configuration was written to disk. claude mcp list is the command that tells you the truth, because it prints a health status beside each server: ✔ Connected, ! Needs authentication, or ✘ Failed to connect. A failure status means Claude Code could not reach that server, not that the list command broke. Inside a session, /mcp gives the same view per server plus the tool count.

Each call to an MCP server stands on its own and carries whatever it needs, which is why an MCP server does not remember your previous request. That is a design choice with a consequence you inherit: any state worth keeping has to live behind the server, in a database or a file, and that is a thing you now operate.

An MCP server is a process you have to run

Here is the cost the vendor comparisons leave out. A skill is a file. An MCP server is software that runs somewhere, and when that somewhere is your VPS (virtual private server), you own its uptime.

A stdio server is the cheap case. Claude Code spawns it as a child process when the session starts, and it dies when the session ends. Nothing to monitor, nothing to patch on its own schedule. A remote HTTP server is a long-lived service, and it needs what any long-lived service needs.

[Unit]
Description=Notes MCP server
After=network-online.target
Wants=network-online.target

[Service]
User=mcp
WorkingDirectory=/srv/notes-mcp
ExecStart=/usr/bin/node /srv/notes-mcp/dist/server.js
Environment=PORT=8931
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now notes-mcp
systemctl is-active notes-mcp
journalctl -u notes-mcp -n 50 --no-pager

systemctl is-active should print active. If it prints failed, the journal holds the reason, and on a first run it is almost always a missing environment variable or a port already bound by something else. Restart=on-failure is not optional here, because a crashed MCP server does not announce itself. You find out when the agent tells you it cannot read your issue tracker.

Bind the process to 127.0.0.1 and put a reverse proxy with TLS (transport layer security) in front of it. An MCP server that reaches your database and answers on a public port with no authentication is a database you have published. Running an MCP server on a VPS covers the proxy, the certificate and the firewall side properly.

Then count the recurring work honestly. The service takes security updates on its own schedule, unrelated to the agent that talks to it. Its OAuth token expires, and claude mcp list starts printing ! Needs authentication at an inconvenient moment. Its credentials sit in a config file or an Authorization header, so they need the same care as any other secret, which is a whole subject on its own: keeping secrets out of an AI agent's reach. None of that work exists for a skill.

Weigh it against the alternative before you build. If the data behind the proposed server changes about once a quarter, a skill that tells the agent where to look and what the fields mean is cheaper than a service you have to keep alive.

How to measure your own context cost

Stop estimating and run /context inside a session. It prints the startup breakdown: system prompt, memory files, tools, and MCP servers, with the token weight of each.

Check two things. Under Memory files, confirm every rules file you expect is listed. A missing file is invisible to the agent, so that is the first thing to rule out when instructions are being ignored. Then look at what your servers cost. If a server you use twice a month is one of the largest lines in that list, toggle it off in /mcp and turn it back on for the sessions that need it. The configuration is kept either way.

A remote server can also report a status like cached 2h ago · connects on first use · 5 tools. That means Claude Code read the tool list from a previous session instead of connecting at startup, and it will connect the first time a tool is called. The tools are available from your first message, so there is nothing to fix. Set MCP_DISCOVERY_CACHE=0 if you would rather every server connect at startup. For the wider picture, managing the Claude Code context window covers what survives compaction, and what those tokens actually cost you turns the numbers into money.

Why does my skill never trigger?

The usual cause is the description. It is the only text in context before the skill runs, so if it does not name the situation, nothing matches it. Write the trigger into the sentence: "Use when the user asks what changed, wants a commit message, or asks to review their diff." Vague descriptions fail silently, which makes this hard to notice.

The second cause is a frontmatter typo, and this one is loud. An unknown key is rejected outright:

Unexpected key(s) in SKILL.md frontmatter: argument-hint. Allowed properties are: allowed-tools, compatibility, description, license, metadata, name

The third is location. Project skills load from .claude/skills/ in your working directory and in every parent up to the repository root. Skills in nested directories below where you started are not loaded at launch. They appear the first time the agent reads or edits a file inside that subdirectory, so until then they do not autocomplete and cannot be invoked by name.

The MCP equivalent of this silent failure is a .mcp.json entry with a url and no type. Claude Code reads any entry without a type as a stdio server, so it skips the entry and reports:

MCP server "notes" has a "url" but no "type"; add "type": "http" (or "sse" / "ws") to this entry

Using all three together

These mechanisms are not competing for the same slot. A setup that works uses each one where it is cheap. The rules file holds a handful of lines that are true everywhere. Skills hold the procedures and load only when they apply. One MCP server, occasionally two, connect the systems whose contents you cannot predict in advance. If you are still building your mental model of the first of those, what an agent skill actually is covers the format in detail.

One test settles most arguments about where something belongs. Delete it, start a fresh session, and give the agent the task. If the agent is merely slower, it belonged in a skill. If the agent is confidently wrong, it belonged in the rules file. If the agent cannot get the information at all, you needed the server, and now you also need a plan for keeping that server up.

FAQ

Should I write a skill or stand up an MCP server?

Decide by whether the information changes between one invocation and the next. If the agent must read live state that somebody else can edit, such as an issue tracker, a database or a dashboard, you need an MCP server, because anything you write down is stale as soon as the record changes. If you could write the answer down once and still be right in six weeks, write a skill. The skill is a file in git with no process to run, no port to expose and no patch schedule, so it is the cheaper option whenever it is possible at all.

Do MCP servers still fill up my context window?

Much less than they used to. Tool search is enabled by default in current Claude Code, so only tool names and the server's instructions field load at session start, and the full schemas are fetched when Claude searches for them. Upfront loading still happens when tool search is off: with ENABLE_TOOL_SEARCH=false, with ANTHROPIC_BASE_URL pointed at a proxy that is not first party, or on a model older than the Claude 4.5 generation. Run /context to see which situation you are in, because the numbers in older comparison posts assume upfront loading.

Does Claude Code read AGENTS.md?

No. Claude Code reads CLAUDE.md. If your repository already has an AGENTS.md for other agents, point one at the other instead of keeping two copies. Run ln -s AGENTS.md CLAUDE.md for a plain symlink, or put @AGENTS.md on the first line of a CLAUDE.md and add Claude-specific instructions below it. Then start a session and run /context to confirm CLAUDE.md shows up under Memory files.

Why did my skill stop having any effect halfway through a session?

Auto-compaction is the usual reason. When the conversation is summarized, Claude Code re-attaches the most recent invocation of each skill, keeping the first 5,000 tokens of each, within a combined budget of 25,000 tokens across all of them. It fills that budget starting from the most recently invoked skill, so if you have invoked several large skills, the older ones are dropped completely. Invoke the skill again to restore its full content.

How do I stop a long rules file from loading in every session?

Move the parts that only matter sometimes into .claude/rules/ files with a paths field in their frontmatter, so each one loads only when the agent touches a matching file. Splitting the file into @path imports does not help, because imported files are expanded and loaded at launch alongside the file that referenced them. Anything that is a multi-step procedure rather than a standing fact should become a skill instead, since a skill body costs nothing until it is invoked.