How to Build Your Own MCP Server (in About 90 Minutes)
A hands-on, 90-minute guide to building your own MCP server in Python: scaffolding, typed tools, wiring to Claude Code, and the hardening most quickstarts skip.
Most AI agent tutorials stop at installing someone else's MCP server. They show you a config block, a single claude mcp add command, and call it done. This guide is the part they skip: how to build your own MCP server from scratch, in about 90 minutes, using nothing but Python and the official SDK. By the end you will have a working tool your agent can call, with a real schema, real auth, and zero magic.
I learned this the hard way. For months I let my self-hosted agents talk to my blog database through a brittle custom HTTP endpoint I wrote in an afternoon. No schema, no standardized auth, no contract. It broke four times in six weeks, usually the moment I renamed a column. The fix was not a better endpoint. The fix was to stop hand-rolling integrations and build your own MCP server that exposes the database as a typed tool.
Why build your own MCP server instead of installing one
The ecosystem already ships excellent servers for the obvious SaaS targets. There are community servers for Slack, GitHub, Postgres, Google Drive, and a dozen more. If your agent needs to read a public repo or post to a channel, install the existing server and move on.
The gap appears the moment your agent needs data that is actually yours. A Postgres table of your own analytics. An internal API nobody published a client for. A self-hosted service running on your Hetzner box. None of these have an off-the-shelf server, because nobody else has your schema. That is precisely where the skill of writing your own server pays for itself.
Here is the deciding question I now ask before touching any integration: does a maintained server for this already exist? If yes, install it. If no, build your own MCP server. That single rule has saved me more debugging time than any framework choice.
What MCP actually is, without the hype
The Model Context Protocol is an open standard for connecting AI systems to the systems where data lives. Anthropic open-sourced it on November 25, 2024, along with the specification, official SDKs, local server support in Claude Desktop, and an open repository of reference servers. Early adopters included Block and Apollo, while developer-tools companies such as Zed, Replit, Codeium, and Sourcegraph began building on top of it.
Under the hood, MCP is JSON-RPC 2.0 carried over one of two transports. The first is stdio, used when the server runs as a local subprocess next to the agent. The second is Streamable HTTP, used when the server lives on a different machine. The protocol defines three primitives that a server can expose: tools (functions the agent can call), resources (data the agent can read), and prompts (reusable message templates). Most builder guides, including this one, start with tools, because tools are where the agent actually does something.
The architecture in three boxes
MCP has a clean shape, and once you see it the rest is plumbing. There is a host, which is the application the user runs (Claude Desktop, Claude Code, or your own Hermes agent runtime). The host holds one or more clients, one per server connection. Then there is your server, the process you write.
The data flow is boring in the best way. The agent decides a tool would help. The client packs that decision into a JSON-RPC request and ships it over stdio or HTTP to your server. Your server runs the function and returns a structured result. The agent reads the result and continues. There is no framework magic, no hidden orchestration layer. You wrote the function. The protocol just delivers the call.
Build your own MCP server: a working example
We will expose a private Postgres table of blog analytics to the agent as a single tool called top_posts. The agent will be able to ask for the highest-traffic posts and get back clean JSON. The whole server is under 40 lines.
Step one is the scaffold. Install the SDK and create a server with a name.
import os
import psycopg2
from mcp.server.fastmcp import FastMCP
DB_DSN = os.environ["DB_DSN"]
mcp = FastMCP("blog-analytics")
@mcp.tool()
def top_posts(limit: int = 5) -> list:
'''Return the top blog posts by views in the last 30 days.
Args:
limit: how many posts to return (default 5)
'''
conn = psycopg2.connect(DB_DSN)
cur = conn.cursor()
cur.execute(
"SELECT slug, views FROM posts "
"WHERE published_at > now() - interval '30 days' "
"ORDER BY views DESC LIMIT %s",
(limit,),
)
return [{"slug": r[0], "views": r[1]} for r in cur.fetchall()]
if __name__ == "__main__":
mcp.run(transport="stdio")
Step two is the part people overthink. In the modern Python SDK, the docstring of your function is the schema. The type hints become the input contract. You do not write a separate JSON schema by hand. I spent an hour convinced I needed a manifest file. I did not.
Step three is connecting it. From Claude Desktop or Claude Code you register the server with a single command pointing at the script. The host spawns it over stdio, reads the tool list, and surfaces top_posts to the model. The first time the agent called my function and got real numbers back, the integration that had broken four times suddenly felt permanent.
Step four is the one every quickstart skips. Give the server a boundary. Over stdio on your own machine, localhost is the boundary. The moment you serve it over HTTP on a VPS, you must add a token and a firewall rule yourself. My first HTTP server sat exposed for two days before an access log showed a stranger probing it. MCP will not secure that for you.
The honest part: what MCP does not do for you
MCP solves transport and schema. It does not solve safety. When your agent calls top_posts, the server runs exactly the code you wrote, with whatever permissions the process has. The protocol happily delivers a destructive SQL statement if that is what your tool does. Tool-call verification, the discipline of checking what an agent is about to do before it does it, lives outside MCP. I wrote about that separately (see Why Your AI Agent Can't Use Tools Safely, and How MCP Fixes It), because the two topics get conflated constantly and they should not be.
The other honest note is operational. Stdio servers are simple and safe but bound to one machine. If you want agents on a remote host to reach your server, you move to Streamable HTTP, and now you own a network service. That is a different job from writing the tool. Plan for it before you ship, not after a log scares you.
Connect your server to the agent you already run
If you self-host agents the way I do, the path is short. Claude Code picks up servers from its config. Claude Desktop does the same from its settings. A Hermes or custom runtime connects through the same SDK client. The server code does not change across any of them, which is the entire point of a standard. Write the tool once, let every host that speaks MCP call it.
For a deeper look at running agents on your own hardware, my write-up on moving off APIs to self-hosted agents covers the surrounding setup, including the VPS choices that keep this cheap.
When to build your own MCP server, and when not to
Build when the data is private, the schema is yours, and no maintained server exists. That is the common case for indie builders and anyone running their own infrastructure.
Do not build when an official or well-maintained server already covers the target. Re-implementing a Slack server is how weekends disappear for no reason. The goal is a tool your agent can call, not a portfolio of servers nobody else will use.
Also do not build a server to wrap a single trivial call you could make yourself in ten lines. MCP earns its keep when the integration is reused, typed, and shared across hosts. If it is a one-off, keep it inline.
Wrapping up
The fastest way to stop treating MCP as a black box is to write one server and watch an agent call it. Pick the ugliest custom integration you currently maintain, the one that breaks when you change a column, and replace it with a typed tool. Ninety minutes of work buys you a contract that does not silently rot.
If you want the full runnable example and a couple of hardening patterns I use in production, it lives in the repo linked from my profile. Start with one tool. The second one takes ten minutes.
Want the next post to cover securing an MCP server over Streamable HTTP on a public VPS? Reply and tell me, and I will write the exact firewall and token flow I run today.