My plan was to spend two hours to wrap the Cheméo REST API as an MCP server.
The REST API is documented with OpenAPI, for me, an MCP server was just the same but formatted differently. My plan was to create one tool per endpoint, transform the request to call the same internal API routines, convert to the right MCP JSON format and hand it back, done. Time estimate: one evening, two if the authentication turned out to be annoying.
It took four evenings, my plan was completed in one, then the reviewer destroyed my work and I took three more evenings to satisfy him. And you know what? The reviewer was a machine. I worked with the help of a machine, to produce software for a machine, and I was judged by a machine. Ok, the machine is a large language model, an agent, AI, whatever you name it. But, for the first time in my life, the only person or system who could objectively judge my work was a piece of software, a machine, not a human.
The context
I need to get over the fact that a machine is judging me, but what is interesting for me, and would probably help someone developing an MCP server today, are the differences with a standard REST API. I developed an MCP server for a very domain specific system, property prediction and a chemical property database. This is not a simple CRUD API where you create an item, update it and delete it, and where nowadays every web framework generates the MCP wrapper for you. Here we handle chemical and physical properties of molecules, which are then used to solve problems with health and safety implications. You cannot just push the data to the agent and expect it (is an agent an it or a him?) to make sense of it correctly, you need to help it.
The answer does not fit
So, starting from what we have, the REST API. Ask the API for ethanol and you get what Cheméo holds on ethanol: 57 properties, 960 measurements, every source, every condition. On the website that is a page you scroll, a huge page. In an MCP client, if you push everything, which was my initial plan, it lands whole in the context window of the model and saturates it. One tool call, context saturated, game over before any real work has started.
An engineer reading that long page skims it and stops when they have what they need. They also build their own context while reading: the citations, the uncertainty printed next to a value, the state of the website itself, all of it gives confidence signals. A model reads everything, pays for everything, and has that much less room for the question it was asked in the first place. The tool is a guest in someone else's context, and my first version behaved like a guest who empties the fridge.
That part I expected, more or less, and cutting a payload down is tedious work I know how to do.
I still did it wrong in the obvious way.
To save room I had moved the references, which included the name of the property prediction method out of the property payload and into a separate tool.
So aspirin came back carrying a critical pressure and a critical temperature, both marked predicted, indistinguishable, one of them a Joback estimate and the other a neural network prediction.
I had saved about twenty bytes and I was paying a full round trip per property to get them back.
The arithmetic was right on each response and wrong over the whole conversation.
Now, you need to pay attention as you continue reading, because we have two different contexts here. The context window of the model, which I had just learned to respect, and the context of the data itself, which is the part I did not see coming.
The answer needs sentences around the numbers
The REST API answers with numbers and facts. On the website that is enough, because a chemist is reading the page and supplying the rest from their own head. The MCP server answers with the same numbers and, next to them, plain sentences saying what each number is, where it comes from, and what it does not mean. Writing those sentences is what took the three extra evenings.
Four examples, and they all have the same origin: something the chemist knows and the agent cannot.
Cisplatin.
I gave the server a molecule picked to be hopeless.
[Pt](Cl)(Cl)(N)N is cisplatin, a platinum coordination complex that decomposes before it melts and never boils.
It came back with twelve numbers: a boiling point of 430 K, a critical temperature of 710 K, an enthalpy of vaporisation of 48 kJ/mol, all flagged predicted, exactly like the aspirin values three calls earlier.
In the same response, Joback declined. No matching groups, no answer. A group contribution method published in 1987 knew it had nothing to say about a platinum complex, and my neural network answered anyway, because a D-MPNN always emits a float, whatever you feed it.
I am not going to fix that in the model, the model does what it does. The prediction is still returned, it now states how far outside the training domain the molecule sits, and it arrives with this line: they are returned rather than withheld so you can decide, not because they are reliable. An agent that reads that sentence does not hand 430 K to the user as the boiling point of cisplatin. Without the sentence, it does.
Propane.
The dipole moment of propane is zero, because the molecule is symmetric.
Zero is falsy (in a programming language, a condition like if 0 returns false), so the measurement was quietly disappearing somewhere between the database and the payload.
A chemist looking at the page sees the missing row and asks why.
The agent told the user, politely and with confidence, that Cheméo holds no dipole moment for propane.
A measured zero is a measurement, and it is reported now, like any other value.
The similarity score. A prediction used to come back with a bare 0.42 in a field. That number says how well the training set covers the molecule you asked about, it says nothing about how accurate the prediction is going to be. Without a sentence saying so, 0.42 goes back to the user as a confidence and 1.0 goes back as a guarantee. So the sentence is in the answer now: max_tanimoto_to_training measures how well the training set covers this structure, not how accurate the prediction is.
The Crippen method. This is the one I like most, because there is nothing to state. Ask whether a method applies to your molecule and there are three answers, not two: we checked and it applies, we checked and it does not, and nobody ever defined a way to check. That third one is the common case for the classical correlations, because the domain of applicability was never in the paper, it lives in the experience of the engineer using the correlation. So the server says exactly that: no domain of applicability is defined for this method, Crippen was published without one; judgement of fitness rests with the caller.
If you keep one thing from this post: where the REST API returns numbers and facts, the MCP server returns the same numbers wrapped in plain sentences explaining what is in the answer. Written like for a human, read by an agent.
Five rounds on three molecules
So, how do you get a machine to review your work?
You do not ask it to look up ethanol. I asked it to use the server the way an agent would, then to tell me whether this is a good server for an agent to use, and above all to tell me what it could not determine from the answers it got back. That last part is the one that produced everything else.
It then went in rounds. The agent probes the server with a handful of calls and writes down what it found, I push back on what is wrong and fix what is right, and it probes again. The session I kept the notes from ran five of these rounds and 22 live tool calls. The first round alone was nine calls, and it came back with 18 findings, two of them serious.
Three molecules did all the work:
- Ethanol, because it is richly measured, 57 properties and 960 measurements.
- Aspirin, because one single answer carries measured values and predicted ones, inside the domain of the model and outside of it.
- Cisplatin, because it is outside everything.
Those nine calls of the first round also surfaced a bug that had been sitting in Cheméo for years. Aspirin's Joback decomposition was wrong: the atom balance was correct, the chemistry was not. The ester had been shredded into a ketone plus an ether oxygen, the carboxylic acid into a ketone plus an alcohol.
The agent did not stop at the observation. It hand calculated what that wrong decomposition predicts, 659.32 K and 413.04 K, and found the same two numbers stored in the database. The bad groups were not a display problem, they had been feeding the stored estimates.
I corrected the two group patterns. The agent recalculated, expected 640.9 K and 433.1 K, and the server answered 640.91 and 433.06. Aspirin's Joback critical pressure moved from 4,083 kPa to 3,577 kPa.
I am fairly sure somebody reported that ester problem to me at some point, and I never found the time to look at it.
The agent was wrong six times
Six times over the cycle, and four of them are worth writing down.
Aspirin's critical pressure. It came back twice in the same answer, from two different methods, and the two values were 2.8% apart. The agent read that as a stale value left behind by a model version bump, and wrote me a plan to reindex and reconcile the database. One of them was a Joback estimate, the other a Relay prediction. Two independent methods landing 2.8% apart is good agreement, there is nothing to reconcile. The fix collapsed from reindexing a database to carrying the name of the method next to the value.
A melting point residual of 21.6 K. Relay predicted a melting point 21.6 K below the curated experimental value, on a molecule with a Tanimoto of 1.0 to the training set. The agent wrote it up as a possible fault in the label pipeline, with two hypotheses and a plan to diagnose it. That residual is normal, and it is evidence the model is not overfitting: a network that reproduced its own training labels closely would be memorising them, and it would do worse on everything else. The 114 measurements behind that curated value make the residual easy to see, they do not make it easier to predict.
Inflating the uncertainty. For predictions outside the training domain, the agent offered to inflate the uncertainty by a stated rule. That cannot be built. Calibrating an inflation factor means measuring the error outside the domain, and that needs out-of-domain ground truth, which by definition does not exist.
The list_properties tool.
This one is my favourite.
In round two the agent found the list_properties tool, concluded it must have been there from the start, and corrected one of its own earlier findings accordingly.
I had added that tool between the two rounds.
So it had to un-correct itself, and out of that came a real finding: from inside a session, an agent cannot tell a tool that was just added from one it failed to notice.
Every answer now names the version that produced it and points at the changelog.
All four are the same case, the agent could see the symptom perfectly and had no access to the cause. I was the opposite. I knew why the data is the way it is, and I could not see the symptom at all, because I have been reading those payloads for years and my eyes fill in the gaps.
Twelve months ago
When I write for a chemist, I can read what I wrote as a chemist. Documentation is harder, but with some effort I can read it as someone who has never opened the software. Here the reader is a language model and I cannot be one. I would look at a payload, find it perfectly clear, and that told me nothing at all, because I know what every field means and I know which ones I do not trust.
The machine could tell me what my answers look like from inside a context window, and there is no way for me to get in there and look for myself. What it could not do is tell me why the data is the way it is, and the four times it tried, it was wrong.
For twenty five years the person deciding whether a piece of my work was good was me, a customer, or another engineer. On this one I designed with a machine, built with a machine, and then asked a machine whether the result was any good. Twelve months ago I would have found that ridiculous. Not the protocol, not the token line on the invoice, the reviewer part.
It also left an item on my list for another evening: ask the agent to help me cross-validate the data in the database.
The server is in beta at https://www.chemeo.com/api/mcp, four tools: search, properties, sources, and prediction from a SMILES.
The changelog moved every day of that week, which is why every answer carries its version.
If you connect it and something reads too clean, it is my wording and not the model.

