Build your own MCP server: the complete guide (2026)
An MCP server is a program that offers tools, data and prompts to AI assistants such as Claude via the Model Context Protocol. Building one yourself takes only about ten lines of Python in 2026, thanks to the official SDKs. This guide walks through SDK choice, a minimal example, connecting to Claude, testing with the MCP Inspector and publishing in the official registry.
When should you build your own and when should you use an existing server?
Building your own is rarely the first step. For common systems — GitHub, Jira, accounting packages, CRMs — ready-made MCP servers already exist; our overview of MCP servers by sector is the place to start. Building your own pays off in three situations: you want to expose an internal system for which no server exists (a legacy API, an in-house database), you want a custom workflow that existing servers don't offer, or you're a vendor who wants to make your product available to AI clients. If you're weighing the trade-off, keep in mind that your own server also means maintenance: the protocol evolves (on 28 July 2026 the biggest — and breaking — spec revision since launch was released, see below) and you are responsible for security yourself — also read our page on MCP security. How the protocol itself works is covered in how MCP works.
Which SDK should you choose?
Since 23 February 2026, the MCP project has operated an official SDK tier system with automated conformance tests, according to the official MCP documentation at modelcontextprotocol.io. Tier 1 SDKs score 100% on the conformance tests, receive new protocol features before a new spec release, and guarantee issue triage within two business days and P0 bug fixes within seven days. There is also demotion: a Tier 1 SDK that lets a conformance test fail for four weeks drops to Tier 2. For business builders the advice is simple: pick a Tier 1 language unless you have a compelling reason not to.
| Language | Tier | Note |
|---|---|---|
| TypeScript | Tier 1 | The de facto standard for npm servers |
| Python | Tier 1 | Package mcp on PyPI, with built-in FastMCP API |
| C# | Tier 1 | Maintained with Microsoft, integrates with ASP.NET Core |
| Go | Tier 1 | Maintained with Google |
| Java, Rust | Tier 2 | ≥80% conformance; Rust's 2026-07-28 support is in beta |
| Swift, Ruby, PHP, Kotlin | Tier 3 | Experimental, no guarantees |
As of September 2026: the four Tier 1 SDKs (TypeScript, Python, Go and C#) fully support the 2026-07-28 spec; the Rust SDK supports the new spec in beta, per the official release announcement. Current tier membership is determined via the MCP project's public conformance tests.
For Python, note a rename that makes older tutorials confusing. In the official Python SDK v2, the high-level server class is called MCPServer (from mcp.server import MCPServer); up to and including v1 it was the FastMCP 1.0 API merged in 2024. Alongside it there is still the standalone FastMCP project (package fastmcp), which according to its own documentation at gofastmcp.com has evolved further with auth providers, OpenAPI generation and deployment tooling. For your first server, the built-in MCPServer from the official SDK is all you need — that's what we use below.
How do you build a minimal MCP server in Python?
The step-by-step plan below follows the official quickstart from modelcontextprotocol.io: a weather server with two tools. Requirements: Python 3.10 or higher and the MCP SDK from 2.0.0. The core idea: you write an ordinary Python function with type hints and a docstring, and the SDK automatically generates the JSON Schema the AI client needs from it.
- Set up the project with uv.
curl -LsSf https://astral.sh/uv/install.sh | sh # install uv uv init weather && cd weather uv venv && source .venv/bin/activate uv add "mcp[cli]" # httpx (HTTP client) ships with the SDK touch weather.py - Define tools in
weather.py.from typing import Any import httpx from mcp.server import MCPServer # SDK v2; was called FastMCP in v1 mcp = MCPServer("weather") NWS_API_BASE = "https://api.weather.gov" @mcp.tool() async def get_alerts(state: str) -> str: """Get weather alerts for a US state. Args: state: Two-letter US state code (e.g. CA, NY) """ ... @mcp.tool() async def get_forecast(latitude: float, longitude: float) -> str: """Get weather forecast for a location. ...""" ... def main(): mcp.run(transport="stdio") if __name__ == "__main__": main() - Run it:
uv run weather.py. The server now waits for a client via stdio.
Besides tools, MCP has two more primitives: resources (read-only data, comparable to files) and prompts (reusable prompt templates). Tools are functions the model can call — with the user's approval — and are the starting point for virtually every server. See the glossary for all the terminology.
How do you connect the server to Claude Desktop and Claude Code?
For Claude Desktop, edit claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %AppData%\Claude\):
{
"mcpServers": {
"weather": {
"command": "uv",
"args": ["--directory", "/ABSOLUTE/PATH/TO/weather", "run", "weather.py"]
}
}
}
Absolute paths, always
The official docs are explicit about this: use the absolute path to your project, and if necessary to the uv executable itself (which uv). On Windows: double backslashes. And restart Claude Desktop after every config change — otherwise the server won't appear. Claude Desktop is not available on Linux.
In Claude Code (CLI) it's done via claude mcp add:
# local stdio server
claude mcp add --transport stdio --env AIRTABLE_API_KEY=KEY airtable -- npx -y airtable-mcp-server
# remote HTTP server
claude mcp add --transport http secure-api https://api.example.com/mcp --header "Authorization: Bearer token"
With --scope project, the configuration goes into a shared .mcp.json in your repository, so the whole team uses the same servers via version control. Options go before the server name; -- separates Claude flags from the server command. You check the status in Claude Code with /mcp.
How do you test and debug an MCP server?
Never test directly through Claude Desktop — every change requires a restart there, which makes debugging painfully slow. The standard first step is the official MCP Inspector: npx @modelcontextprotocol/inspector uv run weather.py launches your server as a child process and opens a web UI on localhost:6274. There you can call tools with arbitrary arguments, browse resources and prompts, and follow the raw JSON-RPC messages live. Once it works in the Inspector, only then connect a real client. If your server still doesn't show up in Claude Desktop, check the logs under ~/Library/Logs/Claude/mcp*.log (macOS); in Claude Code, claude --debug helps.
Beginner mistake #1: logging to stdout
With a stdio server, stdout carries the JSON-RPC stream. A single stray print() or console.log() corrupts that stream and the server breaks silently — according to the official MCP docs, the number one cause of "my server won't start". Log to stderr (print(..., file=sys.stderr), console.error()) or to a file. Only with HTTP servers is stdout logging harmless.
Should you choose stdio or Streamable HTTP?
The rule of thumb: stdio for local and personal use, Streamable HTTP as soon as something needs to be remote, shared or hosted. With stdio, the client launches your server as a child process and JSON-RPC runs over stdin/stdout — no network overhead, but single-client. Streamable HTTP works via a single HTTP endpoint (usually /mcp) that the client POSTs JSON-RPC to and where the server can stream responses; this is the transport for cloud and multi-client servers in 2026. Since spec 2026-07-28, Streamable HTTP is also sessionless: the Mcp-Session-Id header no longer exists, every request is self-contained, and POST requests carry the Mcp-Method and Mcp-Name headers so gateways and load balancers can route without parsing JSON bodies. The older HTTP+SSE model with two endpoints had been soft-deprecated since spec 2025-03-26 and has been formally listed in the deprecation register since 2026-07-28 — don't build anything new on it. To illustrate: Atlassian announced it would switch off its old /v1/sse endpoint on 30 June 2026, according to the Atlassian community forum.
What do you need to know about OAuth 2.1 for remote servers?
As soon as your server runs remotely, the MCP specification mandates OAuth 2.1, with the MCP server acting as the resource server. Required: the Authorization Code flow with PKCE, Protected Resource Metadata (RFC 9728) so clients can discover which authorization servers you trust, and Resource Indicators (RFC 8707) that bind tokens to your specific server. Dynamic Client Registration is now formally deprecated (spec 2026-07-28) in favour of Client ID Metadata Documents, where an HTTPS URL to a metadata document acts as the client ID; DCR remains available as a fallback. The same spec further hardens authorization: clients must validate an iss parameter when present (RFC 9207, against mix-up attacks) and bind stored credentials to the issuer. The good news: frameworks and gateways increasingly take this off your hands — the standalone FastMCP 3 has built-in auth providers, and hosted gateways handle OAuth termination. For the broader security context, including prompt injection and access control, see our security page and MCP and EU regulation.
How do you publish in the official MCP registry?
The official MCP Registry (registry.modelcontextprotocol.io, in preview since September 2025) is the canonical publication channel. Important to understand: the registry hosts metadata, not your code. You publish a server.json that points to your package on npm, PyPI or Docker Hub (or to a remote server URL). Namespaces work via reverse-DNS tied to verified identity: io.github.username/server via GitHub OAuth, or com.yourcompany/server via a DNS or HTTP challenge on your own domain. Only the verified owner can publish under that namespace.
- Package your server: npm (runnable via
npx), PyPI (viauvx) or a Docker image. - Create a
server.jsonwith the name, description, repository and packages. - Verify your namespace:
mcp-publisher login githubor a DNS TXT/HTTP challenge for your own domain. - Publish with
mcp-publisher publish. - Aggregators and marketplaces pick up your server automatically via the registry's REST API.
Note: the registry is still in "preview" (breaking changes and data resets are possible), does not support private servers, and delegates security scanning to npm/PyPI/Docker Hub and to aggregators.
How do you design good tools?
Anthropic's engineering post "Writing effective tools for agents" contains the key design lessons. First: a few good tools beat many thin wrappers — don't mirror your REST API one-to-one, but consolidate workflows (a single schedule_event tool instead of list_users + list_events + create_event). Second: write tool descriptions "as if you were onboarding a new colleague" — the model chooses tools based on the description, not the code; a vague description means a tool that never gets used. Furthermore: unambiguous parameter names (user_id, not user), namespacing per service (jira_search), pagination and truncation with sensible defaults so you don't blow up the context window, and error messages that tell the agent what can be fixed rather than bare error codes. And: evaluate with realistic tasks and read back agent transcripts to find rough edges. More practical advice is in our tips and tricks.
Spec 2026-07-28 is final: build on the v2 SDKs
On 28 July 2026 the final new spec was released — according to the maintainers the biggest revision since MCP launched. The protocol is now stateless: the initialize handshake and the Mcp-Session-Id header are gone, every request is self-contained, and cross-call state runs through explicit, server-signed handles. Roots, Sampling and Logging are deprecated with a window of at least twelve months. The accompanying SDK v2s are now final: Python mcp 2.0 (in which FastMCP has been renamed to MCPServer), TypeScript @modelcontextprotocol/server, plus Go and C#. Advice: build new servers on the v2 SDKs and the 2026-07-28 spec, don't design tools that lean on protocol sessions or the deprecated features, and serve legacy clients through the dual-era model where needed. All the changes are covered in our news article on the stateless spec release.
Further reading
MCP servers by sector
First check whether a ready-made server already exists for your use case.
MCP security
Prompt injection, access control and what to arrange before production.
Trusted sources
The official documentation, registry and specs in one place.
Frequently asked questions
Which programming language is best for an MCP server?
How much code does a minimal MCP server take?
@mcp.tool() and start it with mcp.run(). The SDK generates the JSON Schema automatically.How do I connect my MCP server to Claude Desktop?
claude_desktop_config.json under mcpServers, using an absolute path to your project. Then restart Claude Desktop. In Claude Code you use claude mcp add on the command line.Why won't my MCP server start in Claude Desktop?
print() corrupts the stream. Log to stderr or a file. Also check that you are using absolute paths in the config.What is the difference between stdio and Streamable HTTP?
How do I test an MCP server without Claude?
npx @modelcontextprotocol/inspector uv run server.py opens a web UI on localhost:6274 where you can call tools with your own arguments and watch the raw JSON-RPC messages. Always test here first, then in Claude.How do I publish my MCP server in the official registry?
server.json, verify your namespace (GitHub login or a DNS challenge for reverse-DNS names like com.example/server) and publish with the mcp-publisher CLI. The registry hosts metadata, not your code.Should I build on the new MCP spec of July 2026?
Last updated: