Resources for reading, tools for acting: the MCP design choice most servers get wrong
Every tool you expose over MCP ships its full definition (name, description, JSON schema) into the model's context on every single request, whether that tool ever gets called or not. Expose twenty tools and the agent wades through pages of boilerplate before it has read one of your notes.
That cost shaped the best decision I made building an MCP server in the open this week, and it isn't a feature. It's a split: tools are for acting, resources are for reading. Most MCP servers I've looked at ignore that line and expose everything as a tool. I think that's a mistake. Here's why.
The hidden cost of a tool
Sit with what that per-request tax actually buys you: nothing, most of the time. A note server that exposes "read this note," "list those notes," "get this document," "fetch that attachment" as four separate tools pays for all four on every turn, including the turns that only write. It's quietly burning your context window on plumbing.
MCP already gives you a better primitive for exactly this. It's called a resource.
What vellum MCP does instead
vellum MCP is a self-hosted server over a plain folder of markdown files. Your notes stay as .md on disk, no database, no embeddings. When I designed the surface, I split it clean down the middle.
- Tools are for acting. Writing a note, moving it, patching a section, flipping a task from backlog to done. Fifteen of them, and I keep the number small on purpose. Every one costs context.
- Resources are for reading. Every note is exposed as an MCP resource at a stable URI:
vellum://note/projects/launch.md
That changes two things:
- The agent (or you, in a client's resource picker) can attach a document as context by reference. No tool call to fetch it. You point at the note by name and it's there.
- A client can subscribe to that URI and get a notification the moment the note changes, whether the edit came through an MCP tool or from someone typing in the web editor. The list only re-announces when a note is created, deleted or renamed, not on every keystroke.
The result is an agent that references your documents by name instead of spending a tool round-trip to go get them. Less context on how, more room for what.
This is the whole philosophy, in miniature
I've been calling vellum MCP "deliberately light," and this is what I mean by it. The wins don't come from piling on features. They come from using the right primitive for each job and refusing to add weight that doesn't earn its place.
The same instinct runs through the rest of it:
- Search is a ranked scan in RAM, not a bundled search engine you have to warm up and can corrupt.
- Auth is the server being its own OAuth issuer, no external identity provider.
- The whole thing is one 24 MB container that idles at about 5 MB of RAM.
docker compose up -dand it's running.
None of that is clever. It's mostly saying no.
Try it
vellum MCP is MIT-licensed and self-hostable in one command. If you're building with MCP, the tools-vs-resources split is the one idea I'd take even if you never touch my code.
๐ github.com/freema/vellum
What's your rule for when something becomes a tool vs. a resource on your own server? I'm curious how others draw that line.
Comments
No comments yet. Start the discussion.