How this post was written — the custom MCP server behind this blog
4 min readAI Agents · Developer Tools · Next.js7 views
This post is being published by the system it describes. There is no CMS dashboard with a Publish button in the foreground — it was drafted, validated and published by an AI agent talking to a Model Context Protocol server that lives on this site, at /api/mcp. This is the story of why that exists, what MCP actually is, and how it is wired.
The problem MCP exists to solve
Every agent-to-tool integration used to be one-off glue: custom scripts, bespoke API wrappers, an auth scheme invented per project. The agent re-learned each tool's interface from scratch, every failure had a different shape, and nothing was discoverable. Model Context Protocol is the open standard that replaces that — one consistent interface for a client (your AI coding tool) to discover and call typed capabilities on a server, honestly describing failure and authentication. Created by Anthropic, open-source, and deliberately boring.
What MCP actually is
- JSON-RPC 2.0 messages both ways — requests, responses, errors. Nothing exotic; anything that speaks HTTP or stdio can speak it.
- Three primitives —
tools(actions the agent can take, with typed inputs),resources(read-only context, like documents and guides), andprompts(guided, parameterized workflows). - Two transports —
stdiofor a server running on your machine, and Streamable HTTP for a server on the internet, which is what this blog uses. - Clients and servers — OpenCode, Claude Code/Desktop and Cursor are clients; this blog's
/api/mcpis a server. The wire is the same regardless of who wrote the model.
Why a custom server — and not a generic one
The obvious shortcut is an off-the-shelf bridge from a CMS to MCP. For this site that would have been the wrong tool. The value of this blog is not a database of posts — it is the rules: the honesty bar, the slug discipline, the SEO fields, the block schema, the fact that views is never writable and delete requires an explicit confirmation. None of that exists in a generic adapter. So the custom server reuses the exact same validation (parseBlogInput), the same Mongoose models and the same revalidation calls as the admin panel. The MCP route is a second door into the same building with the same locks — not a side entrance.
How it is wired
/api/mcp— stateless Streamable HTTP with JSON responses, built on the official@modelcontextprotocol/sdkWeb Standard transport. No session state; every request stands alone.- zod schemas for every tool input — then validated again by the shared
parseBlogInputthe admin panel uses. One source of truth for what a valid post is. - MongoDB Atlas for storage — the same
portfoliodatabase andBlogPostmodel as the admin panel and the public site. - Revalidation on every write — mutations call
revalidatePathfor the blog index, the post pages, the sitemap and the RSS feed, so a new post is live within seconds. - Two auth styles — a raw Bearer credential for scripts (the admin password, or a dedicated MCP-only password that never works at
/admin), and a browser password page for interactive clients. - OAuth 2.1 — the sign-in flow serves an
/oauth/authorizepassword page; entering either password mints an opaque, rotating token (PKCE, one-time codes, sha256 at rest). The password never reaches the client. - Audit per credential — every successful create/update/delete is logged with which password issued it; the admin dashboard shows the MCP-password counts.
The connect flow, end to end
Connecting OpenCode to this blog is the one-time part: the first attempt answers 401, /mcps → Sign in opens this site's password page in the browser, the MCP password is entered once, and the client stores an opaque rotating token it refreshes itself. No password is typed again, and nothing is written into a committed config file.
# /mcps → shihab-blog → Sign in → password page → Authorize
# every request afterwards:
POST /api/mcp
Authorization: Bearer <opaque rotating token>What an agent can and cannot do here
- Can — list and read every post by slug, including this one; create a post (validated like the admin panel requires); update a post (partial fields, rename via
newSlug,removeCoverto drop a cover); delete a post — refused unlessconfirm: true; read the writing guidelines and schema as MCPresources. - Cannot — upload cover images (multipart stays in the admin panel); touch projects, messages, settings or site content; write
viewsor any other field outside the schema; rename a slug without the explicitnewSlugfield; bypass validation — there is no tool for that.
What I would tell anyone building one
- Reuse your existing write path. The first mistake is re-implementing validation inside the MCP layer. Import it.
- Stateless beats clever on serverless. No sessions to pin means nothing to leak and nothing to go stale.
- Ship the rules as resources, not just tools — agents write better when the style guide is one
resources/readaway. - Destructive tools need explicit consent —
confirm: trueas a required argument, not a prompt the agent forgets. - Audit every mutation per credential — you want to know which key did what on a shared machine.
None of this makes agents good writers — it makes the guardrails tall and the door narrow. That is the part of the job a good engineer can actually finish. The public docs for this server live at techlife24.store/mcp, no credentials required to read them. And if you're wondering whether this post was really written through the path it describes: it was created by the create_blog_post tool, revalidated by the same revalidatePath calls the admin panel uses, and audited under whichever password signed the request.

Written by
Shihab Hossain — Full-Stack Developer
I build fast, thoughtful products end-to-end — web experiences with React & Next.js, Android apps with Flutter, and the infrastructure behind them.
Work with me