
MCP Deep Dive: A Simple (but Detailed) Guide
AS
Anthony SandeshIf you’ve ever tried to connect an LLM to tools (APIs, databases, file systems, internal services), you’ve probably felt the pain:
- Every app invents its own “tool calling” format
- Every integration is bespoke
- Switching models/clients breaks everything
- Security and permissions get messy fast
Model Context Protocol (MCP) fixes that by standardizing how apps provide tools + context to LLMs. Think: one protocol that lets you plug in many servers (tools/data) into many clients (ChatGPT apps, Claude Desktop, custom agents) without rewriting adapters every time. (OpenAI GitHub)
This post explains MCP end-to-end—simple to understand, but deep enough to build with confidence—and ends with a complete project you can run locally.
1) MCP in one picture (the mental model)
MCP is a client ↔ server protocol.
- MCP Server: exposes capabilities (tools/resources/prompts)
- MCP Client: connects to a server and calls those capabilities
- Host app: the UX that wraps the client (Claude Desktop, ChatGPT app, your CLI, your IDE plugin)
Data flow:
The LLM doesn’t “magically access your DB.” It asks the host/client to call MCP tools/resources, and the host decides what to allow and how to present it. (Model Context Protocol)
2) The 3 core building blocks: Tools, Resources, Prompts
MCP servers can expose three “primitives”:
A) Tools = actions (like POST endpoints)
Tools do work: query an API, run code, write a file, create a ticket, etc.
- Input: structured JSON schema
- Output: structured content back to the client/LLM
(You’ll implement tools in our project.)
B) Resources = read-only context (like GET endpoints)
Resources are chunks of context that clients can add into the model’s working set: files, docs, DB schemas, config, etc.
Key point: resources are app-driven—the host decides how resources are surfaced/selected/included. (Model Context Protocol)
C) Prompts = reusable workflows/templates
Prompts let servers publish “prompt templates” (like slash commands) that users can explicitly choose and run. Prompts are designed to be user-controlled. (MCP Protocol)
3) Under the hood: how MCP actually talks
MCP messages are JSON-RPC
MCP uses JSON-RPC messages (UTF-8) between client and server. (Model Context Protocol)
A JSON-RPC request looks like:
And a response:
Two standard transports
MCP defines two standard ways to move these JSON-RPC messages: (Model Context Protocol)
- stdio
- Client launches server as a subprocess
- Server reads from
stdin, writes MCP messages tostdout - Messages are newline-delimited JSON and must not contain embedded newlines (Model Context Protocol)
- Streamable HTTP
- Server is an independent HTTP service
- Client sends JSON-RPC via POST, may receive streaming responses via SSE
- Includes critical security guidance (Origin validation, localhost binding, auth) (Model Context Protocol)
Rule that bites beginners:
If you use stdio, your server must not print random logs to stdout. Logs should go to
stderr. (Model Context Protocol)4) Designing MCP tools that LLMs won’t mess up
When an LLM picks tools, it’s guessing based on your metadata. So your tool UX matters.
Tool naming
Bad:
doThing
run
process
Good:
notes.search
notes.read
notes.append
notes.create
Tool schemas
Use clear schemas (required vs optional) and tight validation. Most MCP SDKs use Zod for schemas. (Model Context Protocol)
Side effects
If a tool writes/deletes/charges money:
- require explicit user approval in the host (human-in-the-loop)
- log actions to an audit trail
- implement allow-lists and path-safety
5) Why Python + FastMCP is the easiest path
The MCP “Build a server” guide uses FastMCP in Python because it:
- uses Python type hints and docstrings to automatically generate tool definitions (schemas)
- keeps server code minimal and readable
You typically:
- Create
mcp = FastMCP("name")
- Decorate functions with
@mcp.tool(),@mcp.resource(...),@mcp.prompt()
- Run via
mcp.run(transport="stdio")
5a) A tiny “hello tool” example (Python)
That’s enough to show up in an MCP host as a callable tool.
✅ Project: “Smart Notes” MCP Server (Python) + Agent Client
You’ll build:
- An MCP server that manages notes in SQLite:
- Tools: add/list/get/search/delete/export
- Resources: read-only views like
data://notes/indexanddata://notes/{id}
- Prompts: “turn this into a clean note”, “create a study plan from notes”, etc.
- A Python agent (OpenAI Agents SDK) that connects via stdio MCP and uses the tools.
The Agents SDK supports stdio MCP servers using MCPServerStdio.
Setup (uv + MCP SDK)
From the MCP tutorial (Python 3.10+, MCP SDK 1.2.0+), the recommended setup is:
We’ll also use SQLite (built-in) and a little JSON handling (built-in), so no extra deps required.
Project structure
The MCP server: notes_server.py
Important: logging must go to stderr (no print()), because we’re using stdio.
What you just built:
- Tools for CRUD + export
- Resources for “read-only context snapshots” (index, per-note, tag views, stats)
- Prompts to standardize how the host asks the model to transform text
Test the server (2 easy ways)
Option A: Run in an MCP host (Claude Desktop config style)
The MCP tutorial shows adding a server entry that runs via
uv --directory ... run your_script.py.Example config pattern (adapted):
Option B: Use MCP Inspector (best for debugging)
MCP Inspector can be launched via
npx and used to list tools/resources/prompts and test calls.A common pattern is:
In the Inspector UI you can:
- open Tools tab and call
add_note,search_notes, etc.
- open Resources tab and fetch
data://notes/index,data://notes/1
- open Prompts tab and test
clean_note(...)
(Optional but awesome) Build the Python Agent that uses your MCP server
This uses the OpenAI Agents SDK, installed via:
PyPI lists that as the installation command.
The SDK can connect to a local stdio MCP server via
MCPServerStdio and will spawn the process for you.agent_cli.py
Run it:
Now you can say things like:
- “Create a note titled ‘GPU profiling checklist’ with bullet points about Vulkan/CUDA profiling”
- “Search my notes for ‘profiling’ and summarize what I’ve written”
- “Export notes tagged ‘interview’ as markdown”
6) Practical tips that make MCP projects feel “production-grade”
Design tools like APIs
- Use clear names:
add_note,search_notes,export_notes_markdown
- Keep inputs small and explicit
- Prefer structured return values (dict/list) when possible
Put “read-only context” in resources
Resources are a clean way to expose “state views” like:
data://notes/index
data://notes/stats
data://notes/tag/{tag}
That reduces tool calls and helps the model ground itself.
Use prompts to standardize behavior
Instead of re-explaining “how to write notes” in every agent instruction, prompts let you reuse templates reliably.
7) Security and production notes (don’t skip)
If you stay on stdio (local):
- protect the filesystem (allow-list paths, sanitize filenames)
- log to
stderr, never tostdout(Model Context Protocol)
If you move to Streamable HTTP:
Follow the transport spec’s warnings:
- validate
Origin(DNS rebinding prevention)
- bind to
127.0.0.1for local servers
- implement authentication (Model Context Protocol)
If you integrate into OpenAI agents/apps:
OpenAI’s Agents SDK supports hosted MCP tools, Streamable HTTP, and stdio (and notes SSE is deprecated—prefer Streamable HTTP / stdio). (OpenAI GitHub)
ChatGPT Apps also use an MCP server as the backend that defines tools + UI wiring. (OpenAI Developers)
8) Where to take this next (easy upgrades)
Once your notes server works, you can evolve it into something serious:
- Add tags/frontmatter parsing
- Add embeddings + semantic search (tool:
notes.semantic_search)
- Add “read-only mode” vs “write mode”
- Add per-note ACL (only allow writes to
/notes/drafts/*)
- Switch transport to Streamable HTTP when you need remote access


