A no-code walkthrough of livestreaming from your phone to viewers using Amazon IVS, with every …
I Gave My Blog an MCP Server (and You Can Too) I Gave My Blog an MCP Server (and You Can Too)

Summary
My blog now has an MCP server. You can point Claude at https://karandeepsingh.ca/mcp
and it will search my articles, read them, pull up a cheatsheet, or walk a research
workflow — without me pasting anything into a chat window.
The interesting part is how little it took: one serverless function, no database, no session store, and no extra hosting bill. Here’s the build, including the parts I got wrong first.
claude mcp add --transport http kayd https://karandeepsingh.ca/mcp
— or see the MCP server page for the full tool list.Why this is suddenly easy
MCP is stateless at its core: one JSON-RPC request in, one JSON-RPC response out. No sticky sessions, no session affinity, nothing to keep in memory between calls.
That one property is what makes this a weekend project instead of infrastructure. A stateless protocol maps perfectly onto a serverless function — which means the MCP server can live in the same repo as the blog, deploy on the same push, and cost nothing on a free tier. No second thing to run, no drift between two deploys.
Expand your knowledge with How to Replace Text in Multiple Files with Sed
The architecture
The whole thing is:
Hugo build ──► /index.json (full text, ~2 MB — already there for search)
└─► /mcp-index.json (metadata only, ~47 KB — added for this)
Client ──POST──► /mcp (Netlify function) ──fetch──► those two JSON files
That’s it. The blog was already generating a search index for client-side search, so the data layer existed. The MCP server is a thin read-only wrapper over files the CDN was already serving.
Deepen your understanding in Why YouTube-Scale Systems Need SQS: Architecture Notes
Lesson 1: build a trimmed index
My first version read the 2 MB full-text index for everything. That works, but it’s wasteful — most calls (search, list tags, find related posts) only need titles, tags and summaries, not every word of 106 articles.
So I added a second Hugo output format that emits metadata only:
{{- $pages := where .Site.RegularPages "Section" "in" (slice "posts" "cheatsheets") -}}
{
"title": {{ $p.Title | jsonify }},
"url": {{ $p.RelPermalink | jsonify }},
"tags": {{ (or $p.Params.tags slice) | jsonify }},
"readingTime": {{ $p.ReadingTime }},
"summary": {{ (or $p.Params.summary ($p.Plain | truncate 240)) | jsonify }}
}
2 MB → 47 KB. Full text is still fetched, but only when someone actually reads an article. Both indexes are cached at module scope, so a warm container fetches each at most once.
Explore this further in Building a URL Shortener: From Linux Networking to Go
Lesson 2: a tools-only server isn’t a real MCP server
This is the mistake worth writing down. My first version exposed two tools — search and read — and it felt thin. Functional, but boring: you searched, you got a wall of text, and that was the whole experience.
MCP has three primitives, and I’d implemented one:
| Primitive | Who drives it | What it’s for |
|---|---|---|
| Tools | The model | Actions the model decides to call |
| Resources | The client/user | Addressable context you attach directly |
| Prompts | The user | Reusable workflows you pick from a menu |
Adding the other two changed the feel completely.
Resources — every article is now addressable by its real canonical URL:
https://karandeepsingh.ca/posts/{slug}/
https://karandeepsingh.ca/cheatsheets/{category}/{slug}/
Using the actual public URL as the resource URI is a small decision that pays off: the URI is meaningful, dereferenceable in a browser, and needs no translation layer.
Prompts — four workflows people actually want: research a topic with sources, pull up a cheatsheet and answer a question from it, compare two technologies, or summarise what’s new. These are the things I’d otherwise type out by hand every time.
Discover related concepts in Boto3 + AWS Lambda: A Production Serverless Pipeline
Lesson 3: annotate your tools
Every tool on a read-only server should say so:
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
}
Clients use these to decide what needs a confirmation prompt. Without them, a harmless blog search sits behind an “allow?” dialog forever. With them, it can be trusted by default — which is the difference between a tool people use and one they turn off.
Declare capabilities honestly too. If you don’t implement subscriptions, say
subscribe: false rather than advertising something that will fail.
Uncover more details in Linux Automation Tools: From Cron to a Custom Go Runner
Lesson 4: cap output, but paginate instead of truncating
My first read_post hard-capped at 2,500 characters to be polite to the caller’s context
window. It was polite, and it was useless — an 8,500-character cheatsheet came back
chopped mid-command with no way to continue.
The fix isn’t a bigger cap, it’s pagination: return a window, tell the caller where it ended, and let them ask for the next chunk.
_(chars 0–12000 of 18420)_
…more available — call read_post again with offset=12000.
Same protection against dumping a novel into someone’s context, but nothing is lost.
Journey deeper into this topic with Boto3 + AWS Lambda: A Production Serverless Pipeline
Lesson 5: keep links clickable
Early results returned bare URLs. Technically complete, practically annoying — nothing was clickable and the model kept re-formatting titles into plain text.
Now every result is a markdown link, and each read appends a related-posts block:
### [MSK vs Kinesis Cheatsheet](https://karandeepsingh.ca/cheatsheets/cost/msk-vs-kinesis/)
2026-08-06 · 7 min read · aws, cloud, cost
---
**Related posts**
- [Low-Cost Cloud Stack Cheatsheet](…) — _aws, cloud, cost_
It’s a formatting detail that changes how the whole thing feels to use.
Enrich your learning with Linux ls Command: History, Tricks, and Examples
Publishing it
server.json describes the server for the MCP Registry:
{
"name": "io.github.karandaid/karandeepsingh-blog",
"description": "Search and read Karandeep Singh's DevOps and AWS blog: articles and cheatsheets.",
"version": "3.0.0",
"remotes": [
{ "type": "streamable-http", "url": "https://karandeepsingh.ca/mcp" }
]
}
mcp-publisher login github
mcp-publisher publish
Two things bit me here:
mcp-publisher initgenerates an npm/stdio package template. Mine is a remote server, so the wholepackagesblock had to be replaced withremotes. The generatedYOUR_API_KEYenvironment variable was irrelevant too — a public read-only server needs no credentials.descriptionis capped at 100 characters. My first one was rejected for being long.
Also: registry JWTs expire in about an hour, so run login and publish back to back.
GitHub login is only there to prove you own the io.github.<user> namespace — no
organisation or company required.
Gain comprehensive insights from Jenkins LTS vs Weekly: Which Version Should You Use?
What it costs
Nothing. The function runs on the same free tier as the blog, the indexes are static files already on the CDN, and there’s no database. The only real safeguard needed was a conservative per-IP rate limit, because an agent stuck in a retry loop will happily hammer an endpoint.
Master this concept through AWS Lambda Configuration Explained: What to Care About (and Why)
Should you do this?
If you already publish a search index — most static site generators can — you’re most of the way there. The server is a few hundred lines, it ships with your site, and it turns your writing into something an agent can actually use instead of scrape.
The bar for “a website an AI can use well” is about to be higher than “a website with good SEO.” This is a cheap way to clear it early.
Delve into specifics at Jenkins LTS vs Weekly: Which Version Should You Use?
References and Further Reading
- Model Context Protocol. Specification.
- Model Context Protocol. MCP Registry.
- Netlify. Write MCPs on Netlify.
- Anthropic. Connect Claude Code to tools via MCP.
If your site had an MCP server, what would you want it to expose — search, or something more opinionated?
Similar Articles
Related Content
More from devops
How to design an AWS Lambda system for a million users an hour, capacity math, concurrency, cold …
A real, end-to-end walkthrough of Amazon S3 Files, mounting an S3 bucket on EC2 with the s3files …
You Might Also Like
Build a multi-container app with Docker Compose, then build images with Docker Bake and push them to …
Kubernetes CrashLoopBackOff explained: a workflow to diagnose it and fix the six most common causes, …
Learn Kubernetes fundamentals hands-on: deploy your first pod, understand Deployments and …
Knowledge Quiz
Test your general knowledge with this quick quiz!
A set of multiple-choice questions to test your knowledge.
Take as much time as you need.
Your score will be shown at the end.
Question 1 of 5
Quiz Complete!
Your score: 0 out of 5
Loading next question...
Contents
- Why this is suddenly easy
- The architecture
- Lesson 1: build a trimmed index
- Lesson 2: a tools-only server isn’t a real MCP server
- Lesson 3: annotate your tools
- Lesson 4: cap output, but paginate instead of truncating
- Lesson 5: keep links clickable
- Publishing it
- What it costs
- Should you do this?
- References and Further Reading

