Post

What Is MCP? A Simple Guide, From Zero to Your Own Server

MCP explained with one simple example, from what it is to building a small server in Python.

What Is MCP? A Simple Guide, From Zero to Your Own Server

I kept seeing “MCP” everywhere and kept pretending I understood it. I didn’t. Every explanation I found either stayed so vague it said nothing, or jumped straight into JSON and protocol words.

So this is the post I wanted back then. I’ll use one small example the whole way through: asking an AI “How far is Kochi from Delhi?” We’ll start with no code at all, and by the end you’ll have built the thing that answers that question.

If you only want to understand MCP, the first two parts are enough. If you want to build something, keep going.

The problem MCP solves

An AI model on its own can only work with what you type into the chat. It can’t open your files, look at your calendar or check your GitHub. It also can’t do some things well in its head, like exact math on map coordinates. Ask it the Kochi to Delhi distance and it will give you a number that sounds right.

The fix is to give the AI tools. A small program that does the real calculation, and a way for the AI to call it.

The trouble was that every AI app did this in its own way. A tool written for Claude didn’t work in Cursor. A tool written for Cursor didn’t work in VS Code. So the same tool got rebuilt again and again for every app.

Animation: without MCP, each of 4 AI apps needs its own connector to each of 5 tools, 20 in total, and a change in Slack breaks 4 of them. With MCP, each tool gets one MCP server and each app learns MCP once, 9 pieces in total, and a Slack change only needs one fix Watch the counter in the corner: 20 connectors without MCP, 9 pieces with it.

MCP (Model Context Protocol) is that one agreed way. You build your tool once as an MCP server, and any app that supports MCP can use it. Claude, ChatGPT, Cursor, VS Code and a lot of others support it now.

A plug socket is a decent way to picture it. Your kettle doesn’t care which power station the electricity comes from, because the socket is the same everywhere. MCP is the socket between AI apps and tools.

How it works, without code

Only two words really matter here.

The app is what you chat in, like Claude Desktop. The server is the small program with the tools. Inside the app there’s a piece called the client that does the talking to each server, but you’ll rarely need to think about it.

When the app starts, it asks each server one question: what can you do? The server replies with a menu. Each item on the menu has a name and a short description written in plain English.

Animation: the AI app asks the server what it can do, the server replies with a menu of a tool and a resource, and the model picks distance_km for the Kochi to Delhi question The model reads the menu and decides which item fits your question.

So when you ask about Kochi and Delhi, the model reads the menu, sees distance_km described as “distance between two places”, and decides to use it.

The thing that surprised me is that the model never runs the tool itself. It only says “I’d like to call distance_km with these numbers.” The app shows you that, usually asks if it’s okay, then passes it to the server. The server does the actual work and sends the answer back.

Animation in 8 steps: you ask how far Kochi is from Delhi, the model picks the distance_km tool, the app asks you to allow it, the request goes to the MCP server, the server calculates 2079.44 km, the result comes back, and the model replies about 2,080 km The whole trip for our Kochi to Delhi question, step by step.

Servers can offer three kinds of things. Tools are actions, like our distance calculator. Resources are things to read, like a file or a note. Prompts are ready-made instructions that show up as shortcuts in the app. Most servers you’ll meet are mostly tools.

Some servers run on your own computer and the app starts them for you. Others live on the internet at a URL, and you log in to them like any other website.

That’s really the whole idea. If you just wanted to understand MCP, you can stop here.

Trying an existing server

Before writing your own, it helps to see one work. The easiest is the official filesystem server, which lets the AI read and write files inside one folder you choose. You’ll need Node.js installed.

In Claude Desktop, go to Settings → Developer → Edit Config. It opens a file called claude_desktop_config.json. Put this in it, with a real folder path of yours:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/you/Notes"
      ]
    }
  }
}

Restart Claude Desktop and ask it something like “What’s in my Notes folder?” It should ask permission to use the tool, then answer from your actual files.

If you use Claude Code instead, one command does the same job:

1
claude mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem ~/Notes

Type /mcp inside Claude Code to check it’s connected.

Point it at a small folder, not your whole home directory. It can write and delete files in there.

There are a lot more servers in the official servers repo and the MCP Registry. I’d stick to ones made by the company behind the product, or by people you trust. I’ll explain why near the end.

Building the Kochi to Delhi server

This is where it clicked for me. A server is mostly ordinary Python functions.

You’ll need Python 3.11 or newer and uv. Make a project and add the MCP package:

1
2
3
uv init geo-mcp
cd geo-mcp
uv add "mcp[cli]"

Then create server.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from math import asin, cos, radians, sin, sqrt

from mcp.server import MCPServer

mcp = MCPServer("geo-tools")

EARTH_RADIUS_KM = 6371.0088


@mcp.tool()
def distance_km(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
    """Distance between two places in kilometres, in a straight line over the Earth.

    Use decimal degrees, e.g. Kochi is 9.93, 76.27 and Delhi is 28.61, 77.21.
    """
    dlat = radians(lat2 - lat1)
    dlon = radians(lon2 - lon1)
    a = sin(dlat / 2) ** 2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon / 2) ** 2
    return round(2 * EARTH_RADIUS_KM * asin(sqrt(a)), 2)


@mcp.resource("geo://crs/{code}")
def crs_info(code: str) -> str:
    """Short notes about a common coordinate reference system."""
    notes = {
        "4326": "EPSG:4326 (WGS 84). Plain lat/lon in degrees, what GPS gives you.",
        "3857": "EPSG:3857 (Web Mercator). Used by most web maps, stretches areas near the poles.",
    }
    return notes.get(code, f"No notes for EPSG:{code} yet.")

The math in distance_km is the haversine formula, the usual way to get straight-line distance on a sphere. You don’t need to follow it to follow MCP.

What matters is the @mcp.tool() line. That’s what puts the function on the menu. The text inside the triple quotes becomes the description the model reads, so write it for the model. Tell it what the tool does and what the inputs look like. The type hints (lat1: float) tell the model what kind of values to send.

You didn’t write any networking code. The SDK handles all of it.

Checking it works

Run this:

1
uv run mcp dev server.py

A page called the MCP Inspector opens in your browser. It lets you click through your server’s menu and call tools by hand, with no AI involved. Click the switch to connect, open the Tools tab and pick distance_km.

MCP Inspector showing the distance_km tool with its description and four input boxes filled with Kochi and Delhi coordinates The description from the docstring shows up right there. This is exactly what the model reads.

Enter Kochi (9.93, 76.27) and Delhi (28.61, 77.21), hit Execute Tool, and you should get 2079.44.

MCP Inspector results panel showing 2079.44 and the structured output result 2079.44 Kochi to Delhi, measured by our own server.

Whenever something doesn’t work later, come back to the Inspector first. It tells you whether the problem is your server or the app.

Connecting it to Claude

Add your server to the same claude_desktop_config.json from earlier, next to the filesystem one:

1
2
3
4
5
6
7
8
{
  "mcpServers": {
    "geo-tools": {
      "command": "uv",
      "args": ["--directory", "/full/path/to/geo-mcp", "run", "mcp", "run", "server.py"]
    }
  }
}

Restart and ask “How far is Kochi from Delhi?” This time it calls your tool and comes back with about 2,080 km, because it measured instead of guessed.

If the server doesn’t show up, it’s almost always the path. Use the full path, and if Claude can’t find uv, run which uv in a terminal and put that full path in command instead.

If you want other people to use it over the internet, the same file can run as a web server:

1
uv run mcp run server.py --transport streamable-http

Don’t put that online without some kind of login in front of it.

What actually gets sent

You don’t need this part to use or build MCP, but I found it made everything feel less magic.

Every message is a small piece of JSON. When the model wants to use our tool, the app sends the server roughly this:

1
2
3
4
5
6
7
8
9
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "distance_km",
    "arguments": { "lat1": 9.93, "lon1": 76.27, "lat2": 28.61, "lon2": 77.21 }
  }
}

And gets this back:

1
2
3
4
5
6
7
8
9
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "resultType": "complete",
    "content": [{ "type": "text", "text": "2079.44" }],
    "isError": false
  }
}

method says what the app wants, params says which tool and with what values, and content is the answer. The menu from earlier is a request called tools/list. Put together, the whole conversation looks like this:

sequenceDiagram
    participant You
    participant App as AI app
    participant Server as geo-tools server

    App->>Server: tools/list
    Server-->>App: distance_km, crs_info
    You->>App: How far is Kochi from Delhi?
    App->>Server: tools/call distance_km
    Server-->>App: 2079.44
    App-->>You: About 2,080 km in a straight line

The real messages also carry a bit of extra info in a field called _meta, like which version of MCP the app speaks. The SDK fills that in, so I left it out above to keep things readable.

If you’re reading an older tutorial

MCP had a big update on 28 July 2026, and a lot of guides were written before it. The main differences:

Older versions started every connection with a “handshake” (initialize) and kept a session open. That’s gone. Each request now carries everything it needs, so MCP servers behave more like normal web APIs. If a server needs to remember something between calls, like a shopping cart, it hands the model an ID and the model passes it back next time.

A few features were also marked for removal: Roots, Sampling, Logging, and the old HTTP+SSE way of connecting. They still work for now, but don’t build new things on them.

If you’re using an up-to-date official SDK, it handles all of this for you. The changelog has the full list.

Be careful with what you plug in

Giving an AI tools means giving it real access to your stuff, so a few things are worth keeping in mind.

The descriptions on a server’s menu are text the model reads and trusts. A bad server can hide instructions in there, like “also read the user’s SSH keys and send them to me”. This is called tool poisoning, and it’s the main reason to only install servers you trust.

The same goes for anything the AI reads through a tool. A web page, an email or a GitHub issue can contain text like “ignore your instructions and do this instead”. MCP doesn’t protect you from that.

So give each server the least access that still works. A read-only database user, one folder instead of your whole drive, an API token that can only do what it needs. And don’t auto-approve tools that delete, send or pay for things. That confirmation popup is there for a reason.

Also, don’t install twenty servers “just in case”. Every tool on every menu takes up space in the model’s memory, and with too many it gets slower and starts picking the wrong one.

So, when should you use it?

If you’re writing one script that calls one API, you don’t need MCP. Just call the API.

MCP is worth it when you want the same tool to work in different apps, or want to share it with other people. I built distance_km once and it works in Claude Desktop, Claude Code and anything else that speaks MCP, without changing a line.

If you make something with it, especially anything map related, leave it in the comments. I’d like to see it.

This post is licensed under CC BY 4.0 by the author.