How to Build an MCP Server (2026 Guide)
From an empty folder to a tool your agent actually calls — the short path, the real config, and the traps that waste an afternoon.

To build an MCP server you pick a language SDK, write a function, wrap it with a tool decorator, and run it over stdio. That's the whole job for the common case — everything else is polish. This guide takes you from an empty folder to a server your agent actually calls, using real code and the config that gets it loaded.
Before you write anything, know what you're building against: an MCP server is a small program that exposes tools, resources, or prompts to an AI client over a defined protocol. If that sentence needs unpacking, read what an MCP server is first, then come back. The rest of this assumes you know why you want one.
Step 1: Pick an SDK (don't overthink it)
Use the Python or TypeScript SDK unless your host app forces another language. Both are official, both generate the JSON-RPC framing for you, and both ship a high-level API that turns a typed function into a registered tool. Reach for Go, Rust, or C# only when you're embedding the server inside an existing app in that language.
The deeper choice inside Python is FastMCP versus the low-level SDK, and the honest default is FastMCP — it's the same decorator style, and the official SDK even vendors FastMCP v1 as mcp.server.fastmcp.FastMCP. I've written the full decision in FastMCP vs the MCP SDK; the one-line version is below.
| Choice | Use when | Skip when |
|---|---|---|
| FastMCP / high-level SDK | You're exposing tools and want to write logic, not plumbing | You need a custom event loop or client-side protocol control |
Low-level Server | Building a client, deep capability control, embedding | A standard tool server — it's more code for nothing |
| Non-Python SDK | Your host app is Go/Rust/C# | You're free to choose — Python/TS are shorter |
Step 2: Expose one tool, correctly
Start with a single tool and let its type hints generate the input schema. A tool is just a function the model can call; the SDK reads your signature and docstring to tell the client what arguments it takes. Here's a complete, runnable server in Python:
from fastmcp import FastMCP
mcp = FastMCP("weather")
@mcp.tool()
def get_forecast(city: str) -> str:
"""Return today's forecast for a city."""
return fetch_forecast(city)
if __name__ == "__main__":
mcp.run() # stdio by default
The TypeScript shape is the same idea — a server.tool(name, schema, handler) call — with a Zod schema instead of type hints. In both, the docstring or description is not decoration: it's the only thing the model reads to decide when to call your tool. Vague descriptions are the number-one reason a tool sits unused.
Three things that separate a toy from a real tool:
- Return a string or structured content the model can read, not a raw object dump. Format errors as plain sentences.
- Validate inputs and fail with a clear message — the model will retry on a good error and give up on a stack trace.
- Name the tool for intent (
get_forecast, nothandler). The name is part of the prompt.
Step 3: Choose your transport (stdio, almost certainly)
Run over stdio unless you specifically need a network-accessible server. Roughly 90% of the servers we track run locally over stdio — the client launches your process and talks to it over stdin/stdout, so there's no port, no auth, and no deployment. That's what mcp.run() gives you with zero arguments.
Switch to HTTP only when the server must live somewhere else: a shared team instance, a hosted service, or a browser-based client. Then it's mcp.run(transport="http") and you inherit real concerns — auth, TLS, rate limits. The trade-off between the two is laid out in local vs remote MCP; for a first server, stdio is the right answer and you can promote it later.
Step 4: Test with the MCP Inspector before wiring it to an agent
Test your server in the MCP Inspector first — never debug protocol issues from inside a chat client. The Inspector is the official web tool that launches your server, lists its tools, and lets you call them by hand:
npx @modelcontextprotocol/inspector python server.py
It shows you exactly what the client sees: the tool list, each input schema, and the raw result of a call. If a tool doesn't appear here, it won't appear in Claude or Cursor either — so this is where you catch a bad decorator, a schema that won't serialize, or a server that crashes on startup. Iterate here until every tool calls cleanly, then connect it to an agent. Skipping this step is how people spend an afternoon blaming their editor. If a tool still misbehaves once connected, the troubleshooting guide covers the usual client-side causes.
Step 5: Add it to a client
Once the Inspector is green, register the server in your client's config with the command that launches it. For a stdio server that's a command and args entry:
{
"mcpServers": {
"weather": {
"command": "python",
"args": ["/absolute/path/to/server.py"]
}
}
}
Use an absolute path — a relative one breaks the moment the client's working directory differs from yours. The step-by-step for each editor is in how to add an MCP server, and if you'd rather not hand-write JSON, the config generator produces the exact block for your client.
What to skip: don't ship thirty tools
Expose the fewest tools that cover the job — the client, not your server, sets the ceiling. Cursor and similar clients degrade past roughly a 40-tool budget across every connected server combined. A user who installs yours plus four others is near that limit before your server even loads. The math is in Cursor's tool limit.
So fold read variants into one parameterized tool, drop tools the model never picks, and resist the urge to mirror an entire API surface. Good reference points: the official Filesystem server does a lot with a tight set of read/write tools scoped to allowed directories, Context7 is essentially one job — inject current library docs — done well, and the GitHub MCP Server shows how a large surface gets organized when it's genuinely needed. Study a few on the best MCP servers list before you finalize your tool set; the bar for what "good" looks like is set by what's already shipping.
One last note on trust: label your server honestly as community or official, and if it touches the filesystem, network, or credentials, scope those permissions tightly. Users are right to be cautious about what they install — MCP security, what actually matters is worth a read before you publish anything that others will run.