How I Built the MCP Server That Publishes This Blog, Part 1: Why
A decisions journal: why a calendar, why twenty tools, and why a person still presses the button
Written 31 August 2026. This is a decisions journal rather than a tutorial. Everything in it is about one small server that publishes one small blog, and the reason it might be worth your time is that it is a real one with real scars, not a demonstration written to be demonstrated.

Part 1 of the series: why it exists
At the end of the third article in the series I wrote that most teams do not need an MCP server right now, and that the right first step is not to write one but to work out what the tool descriptions will cost in tokens on every request. I believe that. I also wrote one.
So the honest place to start is the contradiction. By my own advice, the publishing half of this server should have stayed a script — and largely it is one, which I will come to. What justified the protocol was the other half, and it took me a while to see which half was which.
What it actually is
What it actually is
A wrapper. It holds no data of its own, and if it disappeared tomorrow the blog would carry on unbothered.
Behind it sits the admin API that already existed, the same endpoints the admin web interface calls. The only difference is how a caller proves who it is: the browser sends a session cookie, the server sends an API key. One API, two authentication methods. Nothing in the calendar was redesigned to be MCP-shaped, and nothing needed to be.
That is worth saying plainly because "build an MCP server" sounds like building a system, and it usually is not. It is writing descriptions of things you can already do, for a reader who has not seen your documentation and never will.
Why a calendar, and not articles
Why a calendar, and not articles
The unit is not an article. It is a dated intention: this text, on that platform, on that day, from that identity, in that state.
The thing I could never hold in my head was never "what should I write". It was what was supposed to go where, on which day, under which account, and whether the thing scheduled for Thursday depended on the thing scheduled for Monday having been indexed first. An entry carries a date, a platform, a type, a publishing profile, a body, and a status that moves from planned to in progress to done or skipped. Types matter more than they look: an article is not a post is not a question is not a comment on somebody's answer, and the last two need a parent to hang off.
Once the unit is a dated entry rather than a text, most of the questions I actually have become listable. What is today. What is planned for this platform this month. What is still sitting at planned with three days to go. None of that is interesting engineering, and all of it is why the thing exists.
Why a model rather than a script
Why a model rather than a script
Here is the part where my own advice cuts against me, and I would rather say it than have a reader notice it.
Publishing is scriptable. Taking a stored body, choosing an adapter by platform, submitting it and writing back the resulting URL is a function, and in this codebase it literally is one: the dispatcher can be called from the protocol or straight from the command line, and it does not care which. A nightly job that reads a file and posts it to an endpoint needs no tool descriptions, no model to read them, and no bill.
Planning is not scriptable, and that is the whole justification. Deciding what goes where on which day, given eighty other entries already in the window and a set of platforms with different tolerances, is a judgement call in which the list of calls genuinely cannot be written in advance. I do not know, when I start, whether the answer is going to be three reads and one update or twenty reads and a rescheduling of half a month. That ignorance is real rather than a habit of mind, and where the ignorance is real the bill is fair.
So the split, stated as plainly as I can: the model plans, the code publishes. Every time I have blurred that line it has cost me something.
Twenty tools, which is five too many
Twenty tools, which is five too many
I have twenty. In the third article I recommended fifteen. Both statements are mine and I am not going to hide the gap.
They fall into four groups. Reading and writing calendar entries, which is the core. Reference lists — publishing profiles, article series — which exist because a model that cannot see the available identities will invent one. Verification, three tools, which is the interesting group and gets its own section. Publishing, which is two tools over the dispatcher. And then a fifth group that has no business being there at all: five tools for a vacancy tracker, a completely different domain that crept into the same server because it was the server that was already wired up.
Nobody decides to have twenty tools. You have eleven, then a shortcut that saves a step, then a listing tool that pairs with an existing one, then a second domain because it was easier than starting a second server, and one morning the list no longer fits on a screen. Every one of them was obviously justified on the day it was added. That is precisely the failure mode: curation is not a decision made once, it is something you keep doing, against yourself, and I have not been doing it.
The tools that have no counterpart in the API
The tools that have no counterpart in the API
The most interesting thing in this server is a set of tools the underlying API does not have and does not need: three verifiers.
Two of them run before anything goes anywhere. They know, per platform and per entry type, what the publishing path can actually render, and they refuse an entry that would come out mangled. The third reads a published page back afterwards and complains.
No REST endpoint would ever have these. A human would not need them either — a person about to paste an article into an editor can see that the table looks wrong. They exist because the caller is a model, and because I wanted a guardrail it could not talk its way past. The publishing tools run the check first and refuse an entry with error-level findings, and getting past that requires an explicit force flag, which is a decision a person makes rather than a step an agent skips.
Which is, when you stand back from it, the general answer to "what does the wrapper add that the API did not have". Descriptions written to be read. A deliberately narrower set of actions than the API exposes. And a point where somebody can look at what is about to be sent and say no.
Why a person still presses the button
Why a person still presses the button
Medium never publishes by itself in this system. The adapter creates a draft and stops. A human opens it and presses publish.
That is not caution for its own sake, it is an asymmetry. A dev.to article, a Telegram message and a Stack Exchange question all go out live, because those platforms make a bad post fixable or deletable at a cost I am willing to pay. A Medium story that goes out truncated has a URL that people have already seen, and re-uploading it replays the same race that broke it. The rule I ended up with is not "always confirm" and not "never confirm". It is: automate to the last reversible step, and let the irreversible one be a decision.
The search I deliberately did not build
The search I deliberately did not build
The most useful decision in this project is a tool that does not exist.
There is a second, tiny server alongside the main one that reads a message archive. It has exactly one tool: fetch messages by key, with an optional number of neighbours for context. It cannot search. That was deliberate. Searching lives in the admin interface's query builder, where you write SQL, see the SQL, and fix the SQL when it returns the wrong thing. A second opaque search — one where you ask, results come back, and you have no way to see what was matched or missed — is exactly the thing that made an earlier chat feature useless enough to remove.
The general form of that decision is the one I would keep if I threw everything else away. Do not put a tool in front of a model when the value of the underlying thing is that a human can see it working.
What is in part two
What is in part two
What broke. Three things, and the third one is still broken as I write this: a check in my own guardrail that reports success about platforms it has never heard of, which is the exact failure this whole series is about, sitting in the code that was supposed to prevent it.
Part two is what broke and what it taught me. The full write-up of the truncation incident is here. The three protocol articles are where MCP and REST came from, what MCP costs in tokens, and what an MCP server is made of.
Sources
Sources
My own promotion-calendar MCP server, its README, and the publishing runbook docs/publishing.md. The MCP specification is published at modelcontextprotocol.io; this used revisions 2025-06-18 and 2026-07-28.