My taste profile lives in the cloud. That is a deliberate choice: it is a paragraph about what I enjoy, and a model that always carries it is worth more than the risk. My stock list is different. It records what is in my house, how much of it, and roughly what it is worth. I don’t want to paste that anywhere.
Which is the problem MCP solves. The file stays where it is, and the model queries it through a small program running on my own machine.
The job
A one-file server with no dependencies that can do three things: hand over the whole list, search on a term, and count by style. Read only. No way at all to change anything, because otherwise one misread sentence is enough to rewrite your records.
My actual prompt
Write an MCP server in a single Node file, no npm packages,
that exposes my cellar CSV.
Three tools, all read-only: whole list, search on text, count by style.
No tool that writes, deletes or edits.
Speak JSON-RPC over stdin and stdout: initialize, tools/list, tools/call.
Re-read the CSV on every call, not once at startup.
When a search finds nothing, return a sentence that explicitly says there is
nothing in the cellar resembling it.
The full file is at sparkone.nl/tools/kelder-mcp.mjs. Hooking it into Claude Code goes like this:
claude mcp add kelder -- node /path/kelder-mcp.mjs /path/kelder.csv
After that you can ask what should be opened, and the model looks in your file before it says anything.
Taken apart
What I learned here is about MCP generally, not about wine.
The tool description is the prompt. The first tool says “use this before recommending anything”. That is not documentation for a human. It is the instruction the model uses to decide whether to call the tool at all. A vague description gets you a model that skips the tool and answers from memory, which is the exact thing your own server was meant to prevent.
The empty answer is the most important line. Search for something that isn’t there and you get: there is nothing in the cellar resembling this. Returning an empty list is easier to write and more dangerous to use, because a model that gets nothing back fills the gap with whatever it thinks it knows about your cellar.
Read-only is an architecture choice, not a setting. There is no write tool, so no sentence exists that can alter your file. That is stronger than any warning in a system instruction.
Re-read on every call. A server that loads its file at startup will happily serve the old answer after a change until you restart it. With a list you maintain by hand, that is the most likely scenario of all.
What broke
Hooking it up worked first time, which was suspicious. So I tested it by pushing JSON-RPC in by hand instead of going through Claude, and the sharp edges came up.
The count by style returned “rood zwaar: 1”. That is the same bottle the drinking-window script had rejected a week earlier, because that style is not in its list. Two pieces of tooling on one file, with different strictness. The server counts what is there, the script refuses what it doesn’t recognise, and that contradiction sat unannounced inside two different answers. I renamed the bottle, but the lesson is wider: the moment two tools read the same file, you need to know which one is the strict one.
I had also invented one tool too many. My first version included a tool that advised what to open, with the drinking-window logic inside it. That is precisely the work the model does better once it has the data. An MCP server should be boring: it hands over facts, and the judgement happens above it.
Ethics note
This is the side of AI I find most neglected, and the reason I built it. There is a difference between data you give up because you get something back, and data you give up because it was the easiest path. A stock list belongs in the second category, and a hundred lines of code means it no longer has to.
Be honest about what still leaves. When the model answers your question using your cellar, the tool’s output goes to the service. What stays local is the file, not the conversation. If you want that closed too, you end up at a model running on your own machine, and you give up a lot of quality for it.
And a third: read-only protects your file, not your attention. A model that knows your stock is a model that finds it easier to suggest opening something. I added a rule that it should first ask whether I actually want to drink tonight.
Take it
You don’t need a cellar for this. Any list you maintain by hand works: books, tools, plants, expenses.
Write an MCP server in a single Node file with no npm packages that
exposes [my file].
Read-only tools: [three things you want to be able to ask].
No tool that writes, deletes or edits.
JSON-RPC over stdin and stdout: initialize, tools/list, tools/call.
Re-read the file on every call.
On no result: return a sentence that explicitly says nothing resembles it.
A small exercise for today: test your server by hand rather than through a chat window, by sending three lines of JSON into it. You only see what a model makes of it once you have seen what actually comes out.
