The first version of my MCP server worked and was still useless. Every tool returned correct data when I called it by hand in the Inspector, and then I pointed Claude at it and watched the model do the wrong thing about a third of the time. It would search for a value it already had an exact column name for, page through rows one at a time, or invent a column called Name for a sheet whose column was Student Name.
None of that was a bug in my code. The handlers were fine. What was wrong was everything I had written about the handlers: the tool names, the descriptions, the argument docs, and the error strings. The model only ever sees those. It never sees your implementation, so your implementation is not what it is reasoning about.
MCP, the Model Context Protocol, is the standard way AI clients like Claude and Cursor talk to outside data. An MCP server advertises a fixed list of tools, and the client can only call what that list contains. I build PasteSheet, which publishes a Google Sheet as a read-only MCP server, so my whole surface is three tools over a spreadsheet. That turns out to be enough to get wrong in a lot of instructive ways.
One disclosure before the code. My server is written in PHP, not Python. The lessons below are protocol-level rather than language-level, and I am showing them in Python because the official SDK is what most people reach for when they build one of these. Everything here is a real decision I shipped, translated.






