A hands-on tutorial
How to build an MCP server: a step-by-step guide
To build an MCP server: install an official MCP SDK, declare your tools with typed inputs, optionally expose resources and prompts, run the server over stdio or HTTP, then connect an MCP client like Claude and test it. A minimal Python server is about ten lines; the work is in choosing what to expose and validating every input.
This is the build. For what MCP is, its three primitives, and how it differs from an API, start with what is the Model Context Protocol — this page assumes that and goes straight to code.
Prerequisites
You need very little to get a server running locally:
- A language with an official SDK. Python and TypeScript are the most mature; the same protocol is also implemented for other languages. This guide uses the Python SDK (the secondary path most people search for), with notes on where the TypeScript SDK is equivalent.
- Python 3.10 or newer and uv (recommended) or
pipto manage the environment. - An MCP client to test against — Claude Desktop, or the MCP Inspector that ships with the SDK. You do not need cloud credentials to build or run the server itself.
Conceptually a server exposes three things — tools (model-callable functions), resources (readable data), and prompts (reusable templates). The steps below add them in that order. Exact SDK signatures evolve, so treat the snippets as the current shape and check the live docs before shipping.
Step 1: scaffold the server
Create a project, install the SDK, and write the smallest server that runs. With uv:
uv init weather
cd weather
uv venv
source .venv/bin/activate
# Install the MCP SDK (with the CLI extras) plus anything your tools need
uv add "mcp[cli]" httpx
Then create server.py with the server object and an entry point. FastMCP is the high-level Python API: you name the server and run it over a transport.
from mcp.server.fastmcp import FastMCP
# Name the server; clients see this name on connect
mcp = FastMCP("weather")
if __name__ == "__main__":
# stdio is the default local transport
mcp.run(transport="stdio")
That already runs — it just exposes nothing yet. In the TypeScript SDK the equivalent is creating an McpServer from @modelcontextprotocol/sdk and connecting it to a transport; the structure is the same, only the syntax differs.
Step 2: define a tool
A tool is a function the model can decide to call. In the Python SDK you decorate a normal typed function: the type hints become the input JSON schema and the docstring becomes the description the model reads to decide when to call it.
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers and return the sum."""
return a + b
Real tools wrap something useful — a database query, an internal API, a file operation. The handler is just a function, so this is where you call your existing service and return a result the model can use:
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""Get the weather forecast for a location."""
data = await call_weather_api(latitude, longitude)
return format_forecast(data)
Two things matter here, both architectural: the schema is the contract the model fills in, so keep argument names and descriptions precise; and the function runs with whatever privileges the process has, so scope it to the minimum it needs. We come back to that in Step 5.
Step 3: add resources and prompts
Tools take actions; resources expose data the model can read, and prompts are reusable templates. Not every server needs all three — add what your use case calls for.
A resource is addressed by a URI. It can be static, or a template with parameters the client fills in:
@mcp.resource("config://app")
def get_config() -> str:
"""Static configuration the model can read."""
return "weather-app v1.0"
@mcp.resource("weather://{city}/current")
def current_weather(city: str) -> str:
"""Current conditions for a named city."""
return load_conditions(city)
A prompt is a parameterized template the server publishes so common workflows are authored once, not re-written in every client:
@mcp.prompt()
def summarize_forecast(city: str) -> str:
return f"Summarize the weather outlook for {city} in two sentences."
The distinction is deliberate: a client may surface resources and prompts to the user (to pull into context or pick from a menu) while letting the model invoke tools autonomously. Keeping them separate is what makes a server predictable across clients.
Step 4: run and connect it
There are two transports you will actually use:
- stdio — the server runs as a local subprocess the client launches. This is the default for desktop clients and local development:
mcp.run(transport="stdio"). - Streamable HTTP — the server runs as a web service a remote client connects to, for hosted or shared servers:
mcp.run(transport="streamable-http"). Transport names and options change as the spec evolves, so confirm the current one in the docs.
To connect a local stdio server to Claude Desktop, point its config at the command that launches your server. The fastest path is to let the SDK install it for you:
uv run mcp install server.py
Or write the entry by hand in claude_desktop_config.json (use an absolute path):
{
"mcpServers": {
"weather": {
"command": "uv",
"args": ["--directory", "/ABSOLUTE/PATH/TO/weather", "run", "server.py"]
}
}
}
Restart the client and your tools, resources, and prompts appear. The server is now part of the tools/action layer of whatever agent connects to it — reusable across any MCP-compatible client, not bound to one app.
Step 5: test and secure it
Test both halves of the contract — discovery (does the client see your capabilities?) and execution (do calls run and return correctly?). The SDK ships an inspector for exactly this, with no client wiring needed:
uv run mcp dev server.py
That opens the MCP Inspector, where you can list the tools, resources, and prompts the server advertises and invoke them with test arguments before any model touches them.
Then treat the server as security-critical, because it executes on behalf of an untrusted caller — the model's arguments can be wrong, malformed, or steered by prompt-injected content the model just read. The non-negotiables:
- Validate every input. Check arguments against the schema and your own constraints before acting. The model's output is untrusted input by definition.
- Least privilege. Scope each tool's credentials and reach to the minimum it needs, so a confused or hijacked call cannot reach further than intended.
- Human-in-the-loop on the irreversible. Require explicit approval for anything you cannot easily undo — deletes, payments, outbound messages.
From here, the natural next step is composing this server into a full agent: how the tools/action layer sits among orchestration, memory, and retrieval is covered in agentic AI architecture, and the broader skill set is in the curriculum.
Frequently asked questions
How do you build an MCP server?
Install an official MCP SDK, create a named server object, declare tools as typed functions (the type hints become the input schema), optionally expose resources and prompts, run the server over stdio or streamable HTTP, then connect an MCP client such as Claude and test discovery and execution. A minimal server is around ten lines; the real work is choosing what to expose and validating every input.
What language can you build an MCP server in?
Any language with an MCP SDK. Python and TypeScript are the most mature and best documented, and there are SDKs for several other languages. MCP is a protocol, not a library, so a server written in one language interoperates with clients written in any other — pick the language your existing tools and APIs already live in.
How do you build an MCP server in Python?
Install the SDK with uv add "mcp[cli]", then create a FastMCP("name") server. Decorate typed functions with @mcp.tool() for actions, @mcp.resource("uri") for readable data, and @mcp.prompt() for templates, and call mcp.run(transport="stdio"). The type hints and docstring become the tool's schema and description automatically. Confirm signatures against the current SDK docs, which evolve.
How do you connect an MCP server to Claude?
For a local stdio server, add it to Claude Desktop's claude_desktop_config.json under mcpServers, pointing command and args at how your server launches — or run mcp install server.py to have the SDK write that entry for you. Restart the client and it discovers your tools, resources, and prompts. A remote server uses the streamable HTTP transport instead.
How do you test an MCP server?
Use the MCP Inspector that ships with the SDK — mcp dev server.py — to list what the server advertises and invoke tools with test arguments, before any model is involved. Verify both discovery (the client sees your capabilities) and execution (calls run and return correctly). Then connect a real client like Claude and exercise the same paths end to end.
How do you secure an MCP server?
Treat the server as code that runs on behalf of an untrusted caller. Validate every tool input against its schema and your own constraints, never trusting model-supplied arguments. Apply least privilege so each tool reaches only what it must, and require human approval for irreversible actions. Prompt injection is the concrete threat: content the model reads can try to steer the tools it calls.
- MCP Python SDK — server build steps, the
FastMCPAPI, and themcpCLI (Inspector / install): modelcontextprotocol.io/docs/develop/build-server and github.com/modelcontextprotocol/python-sdk (verified 26 Jun 2026). - Protocol, primitives, and transports: the Model Context Protocol specification at modelcontextprotocol.io. TypeScript equivalent: github.com/modelcontextprotocol/typescript-sdk.
- Security framing (validate inputs, least privilege, human-in-the-loop, prompt injection) synthesized from the spec's security guidance and AI Architect Academy's curriculum (tool use and safe integration).
Exact SDK signatures and transport options evolve — check the current spec before shipping. Corrections: hello@aiarch.dev.
Build tool-integration skills, not just a demo server.
AI Architect Academy teaches tool use, agentic design, and safe integration as first-class skills — across Anthropic, AWS, and Cloudflare — on a platform that is itself a production agentic system built in public. The build is the curriculum.
Free sample — no signup · every claim cited · cancel anytime
Or get notified when new tracks ship.