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 (a breaking spec release is coming at the end of July 2026, 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.

LanguageTierNote
TypeScriptTier 1The de facto standard for npm servers
PythonTier 1Package mcp on PyPI, with built-in FastMCP API
C#Tier 1Maintained with Microsoft, integrates with ASP.NET Core
GoTier 1Maintained with Google
Java, RustTier 2≥80% conformance, new features within 6 months
Swift, Ruby, PHP, KotlinTier 3Experimental, no guarantees

For Python, note that two flavours of FastMCP exist — a well-known source of confusion. The official Python SDK contains the FastMCP 1.0 API merged in 2024 (from mcp.server.fastmcp import FastMCP). Alongside it there is 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 variant 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 1.2.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.

  1. 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
    touch weather.py
  2. Define tools in weather.py.
    from typing import Any
    import httpx
    from mcp.server.fastmcp import FastMCP
    
    mcp = FastMCP("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()
  3. 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. The older HTTP+SSE model with two endpoints has been deprecated since spec 2025-03-26 — don't build anything new on it. To illustrate: Atlassian is keeping its old /v1/sse endpoint alive only until 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 has been deprecated since spec 2025-11-25 in favour of Client ID Metadata Documents. 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.

  1. Package your server: npm (runnable via npx), PyPI (via uvx) or a Docker image.
  2. Create a server.json with the name, description, repository and packages.
  3. Verify your namespace: mcp-publisher login github or a DNS TXT/HTTP challenge for your own domain.
  4. Publish with mcp-publisher publish.
  5. 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.

Heads-up: spec 2026-07-28 and the SDK v2 betas

On 28 July 2026, a breaking spec release lands that makes the protocol stateless (no more initialize handshake and Mcp-Session-Id) and deprecates Roots, Sampling and Logging with a twelve-month window, according to the official MCP blog. The v2 betas are already out: Python mcp[cli]==2.0.0b1 (in which FastMCP is renamed to MCPServer), TypeScript @modelcontextprotocol/server@beta, Go v1.7.0-pre.1 and C# 2.0.0-preview.1. Advice: build on the stable v1 SDKs today, but don't design tools that lean on protocol sessions or the features slated for deprecation.

Further reading

Frequently asked questions

Which programming language is best for an MCP server?
Preferably pick a Tier 1 SDK: TypeScript, Python, C# or Go. These score 100% on the official conformance tests and receive new protocol features first. For beginners, Python with FastMCP is the shortest route: a working server in roughly ten lines of code.
How much code does a minimal MCP server take?
With the FastMCP API in the official Python SDK, about ten lines: you define a function with type hints and a docstring, decorate it with @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?
Add your server to 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?
The most common cause: logging to stdout with a stdio server. Stdout carries the JSON-RPC messages; a single stray 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?
With stdio, the client launches your server as a local child process — ideal for personal use. Streamable HTTP is the transport for remote and shared servers via a single HTTP endpoint. HTTP+SSE has been deprecated since spec 2025-03-26 and should no longer be used for new builds.
How do I test an MCP server without Claude?
With the official MCP Inspector: 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?
Package your server on npm, PyPI or Docker Hub, create a 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 already build on the new MCP spec of July 2026?
No. The 2026-07-28 spec is a breaking release (stateless protocol) and the accompanying SDK v2s are still in beta. Build on the stable v1 SDKs today, but avoid depending on Roots, Sampling and Logging — those are being deprecated.

Last updated: