I Built a Custom MCP Server That Publishes My Blogs for Me
Why I Started This
I've been deep in AI engineering prep lately — deployed a couple of full-stack AI projects, fine-tuned an open-weight LLM, and been grinding DSA for placements. Somewhere in the middle of all that, I got curious about MCP (Model Context Protocol) and decided to actually build something with it instead of just reading about it.
The idea was simple: what if Claude could read a rough draft blog post sitting on my machine, clean it up, and publish it straight to Dev.to — without me touching the Dev.to UI at all?
Turns out, that's exactly what MCP is for.
What Is MCP, Briefly
MCP is a protocol that lets an AI model like Claude call tools you define — read files, hit APIs, run scripts — instead of just generating text. You write a small server exposing "tools" (plain Python functions with docstrings), plug it into Claude Desktop's config, and suddenly Claude can act, not just talk.
I set up two servers for this:
- filesystem — an official MCP server that lets Claude read/write files in a folder I allow
-
devto — a custom server I built that exposes a
publish_blog_to_devtotool, wrapping the Dev.to API
Setting Up the Environment
I started with uv for Python package management — much faster than plain pip/venv.
First hiccup: PowerShell couldn't find uv right after installing it, because my terminal session was already open before the installer updated PATH. Closing and reopening the terminal fixed that instantly. A small thing, but easy to panic over if you don't know why it's happening.
Second hiccup was more interesting. I created a fresh venv and tried uv add "mcp[cli]", but it kept failing dependency resolution — because my pyproject.toml had requires-python = ">=3.9" left over from when the project was first scaffolded against system Python 3.9, but the actual MCP SDK needs 3.10+. I fixed it by pinning explicitly:
uv python pin 3.12
uv venv --python 3.12
Enter fullscreen mode Exit fullscreen mode
Then I edited requires-python to >=3.10 in pyproject.toml, and the install went through clean.
The Package Name Trap
Here's the one that actually got me for a while. After a supposedly successful install, importing fastmcp kept throwing ModuleNotFoundError: No module named 'mcp.server.fastmcp' — even though import mcp worked fine and the folder clearly had a server directory in it.
It turned out uv pip show mcp revealed the installed package was version 2.0.0, with dependencies like httpx2, mcp-types, pyjwt, and pywin32 — none of which belong to the real MCP SDK. There's an unrelated package squatting the name mcp on PyPI, and it got pulled in instead of the real modelcontextprotocol SDK.
The fix was to pin the version range explicitly:
uv remove mcp
uv add "mcp[cli]>=1.2.0,<2.0.0"
Enter fullscreen mode Exit fullscreen mode
That pulled in the legit SDK (1.29.0 at the time), and from mcp.server.fastmcp import FastMCP finally worked.
Lesson: if uv add somepackage "succeeds" but nothing makes sense afterward, check uv pip show — don't assume the name on PyPI is the project you think it is.
Wiring It Into Claude Desktop
Claude Desktop reads its MCP server list from claude_desktop_config.json, under a top-level mcpServers key. This file has other unrelated app preferences in it now, so it's easy to accidentally add your server as a sibling of mcpServers instead of nested inside it — which silently does nothing. I learned that one the hard way, staring at Settings → Developer → Local MCP servers wondering why only filesystem showed up.
Correct shape:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "C:\\Technical\\mcp-code"]
},
"devto": {
"command": "C:\\Users\\vdine\\.local\\bin\\uv.exe",
"args": ["--directory", "C:\\Technical\\custom-mcp\\devto-mcp-server", "run", "dev-server.py"]
}
}
}
Enter fullscreen mode Exit fullscreen mode
One more Windows-specific gotcha: Claude Desktop doesn't always inherit your shell's PATH, so "command": "uv" alone sometimes fails to launch. Using the full path from where.exe uv fixed it for good.
After a full quit-and-reopen of Claude Desktop (not just closing the window — it lingers in the tray), both servers showed up as running.
Testing It with the MCP Inspector
Before wiring anything into Claude Desktop, I tested the server standalone using:
uv run mcp dev src/mcp_server_demo/__init__.py
Enter fullscreen mode Exit fullscreen mode
This spins up the MCP Inspector — a local web UI where you can call your tools directly and see raw request/response payloads, without needing Claude in the loop at all. Genuinely useful for catching bugs early instead of debugging through a chat interface.
Publishing the Blog
With both servers connected, the actual publishing step is almost anticlimactic — I just talk to Claude normally:
"I have a file blog.txt in [folder], refine its content, then publish it to Dev.to as a draft with relevant tags."
Claude chains the tools itself:
- Calls
read_text_file(filesystem server) to pull the raw draft - Rewrites and cleans up the content inline
- Calls
publish_blog_to_devto(my custom server) with the title, markdown body, and tags
No copy-pasting into Dev.to's editor, no manual formatting. I set published: false first to review as a draft before making it live — cheap insurance against publishing something half-baked.
What This Actually Taught Me
Most of the real learning here wasn't about MCP the protocol — it was standard environment debugging: PATH issues, Python version pinning, a squatted package name, and a JSON nesting mistake. MCP itself, once the environment was sane, was maybe 20 lines of Python with a docstring.
That's probably the underrated lesson: agent tooling is only as reliable as the boring plumbing underneath it. Get the venv, the package versions, and the config shape right, and the "AI does the interesting part" takes care of itself.
Next up, I'm planning to add a couple more tools to the same server — maybe one that pulls my GitHub commit history to help draft "what I built this week" posts automatically.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.