# How to build an MCP server

Here's how to build an MCP server: install an official SDK, write a few functions with clear names and descriptions, mark each one as a tool, and run it over stdio for your own machine or Streamable HTTP for everyone else. With the Python SDK, a server with one working tool is about 15 lines.

The code is the easy part. The hard part is deciding what the tools should be, who's allowed to call them, and what happens when the model calls the wrong one.

And before any of it, check whether someone already hosts a server for the system you want to reach. If they do, you probably shouldn't build one.

- [What you're building](#what-youre-building)
- [Step 1: Pick an SDK](#step-1-pick-an-sdk)
- [Step 2: Write your first tool](#step-2-write-your-first-tool)
- [Step 3: Test it with the Inspector](#step-3-test-it-with-the-inspector)
- [Step 4: Take it remote](#step-4-take-it-remote)
- [Step 5: Add authorization](#step-5-add-authorization)
- [A worked example: a WordPress server](#a-worked-example-a-wordpress-server)
- [When you shouldn't build one](#when-you-shouldnt-build-one)
- [What goes wrong](#what-goes-wrong)
- [Finer points](#finer-points)

## What you're building

An MCP server is a small program that tells an AI app what it can do and then does it when asked. The app (Claude, ChatGPT, Cursor, VS Code) is the client. Your server exposes three kinds of things, per the official docs: tools the model can call, resources it can read, and prompts the user can pick.

Most servers are mostly tools. A tool is a function plus a name, a description and an input schema, and the model reads all three to decide when to call it.

That's why tool descriptions matter more than people expect. **The model never sees your code, only your descriptions.**

## Step 1: Pick an SDK

As of October 2026 the MCP project lists 10 official SDKs. Six are Tier 1, the most complete and best maintained: TypeScript, Python, C#, Go, Rust and Ruby. Java is Tier 2. Swift, PHP and Kotlin are Tier 3.

Pick the language your API client already lives in. If you're starting fresh, Python is the shortest path, and TypeScript is the one most hosted examples use.

For Python you need 3.11 or newer. With uv (or `pip install "mcp[cli]"` if you prefer pip):

```bash
uv add "mcp[cli]"
```

For TypeScript, the current tutorial on modelcontextprotocol.io installs the server package and zod:

```bash
npm install @modelcontextprotocol/server zod
```

OpenAI's plugin docs and the MCP project's own authorization tutorial use a different package, `@modelcontextprotocol/sdk`. Pick one tutorial and follow it end to end rather than mixing snippets from two.

## Step 2: Write your first tool

Here's a complete Python server with one tool. Say you want your assistant to check whether a page on your site is up:

```python
import httpx2
from mcp.server import MCPServer

mcp = MCPServer("site-check")

@mcp.tool()
async def check_page(url: str) -> str:
    """Use this when the user asks whether a page on their website loads.
    Returns the HTTP status code."""
    async with httpx2.AsyncClient() as client:
        r = await client.get(url, timeout=15.0)
    return f"{url} returned {r.status_code}"

if __name__ == "__main__":
    mcp.run(transport="stdio")
```

The type hint (`url: str`) becomes the input schema and the docstring becomes the description. You don't write JSON Schema by hand. (`httpx2` is the HTTP client the SDK already installs.)

Notice the description starts with "Use this when". OpenAI's build guide recommends exactly that pattern, and it helps every client, not only ChatGPT.

One rule from the official tutorial that bites everyone once: in a stdio server, never print to stdout. `print()` in Python or `console.log()` in Node writes into the same stream as the protocol messages and breaks the connection. Log to stderr instead.

## Step 3: Test it with the Inspector

Before you touch a real client, open the server in the MCP Inspector:

```bash
uv run mcp dev server.py
```

Call your tool with a good URL, a broken one and some garbage. Check the error messages read like something a model could act on.

Then add it to a client. In Claude Desktop that means an entry in `claude_desktop_config.json` that runs your script. In Claude Code it's one `claude mcp add` command. Restart the client, ask for something your tool does, and watch whether it gets picked.

If the model ignores your tool, the fix is almost always the description, not the code.

## Step 4: Take it remote

A stdio server only runs on the machine that launched it. That's fine for you. It's useless for a client who uses ChatGPT in a browser.

To serve everyone, you switch to Streamable HTTP, the transport the 2026-07-28 spec defines for remote servers: each message is an HTTP POST to one MCP endpoint, and replies come back as JSON or a short event stream. With the Python SDK you can try it locally:

```bash
uv run mcp run server.py --transport streamable-http
```

Your server is now at `http://localhost:8000/mcp`. For production you deploy it somewhere with a stable HTTPS URL, usually ending in `/mcp`. OpenAI's plugin docs require exactly that for public listings, and they say a temporary tunnel won't pass review.

There's more on what changes when a server leaves your laptop in [remote MCP servers](/learn/remote-mcp-server).

## Step 5: Add authorization

The moment your server touches anyone's private data, it needs to know who's calling.

MCP's answer is OAuth 2.1. When a client connects without a token, your server replies `401 Unauthorized` and points to a Protected Resource Metadata document (RFC 9728). The client reads it, finds your authorization server, the user signs in and clicks allow, and every later request carries a bearer token.

You usually don't build the authorization server yourself. The official tutorial uses Keycloak, and most teams plug in whatever identity provider they already run. The Python SDK's `MCPServer` class publishes the metadata document and hands each token to a verifier you write.

Stdio servers skip all this. They run as you, on your machine, so they can read credentials from environment variables.

## A worked example: a WordPress server

Kitepod (we make it) is a remote MCP server for WordPress, so here's what the steps above looked like for a real one. Treat it as an example, not a template.

- Transport and auth: one endpoint, `https://kitepod.com/mcp`, over Streamable HTTP with OAuth 2.1. Nobody pastes an API key or application password anywhere.
- The other half: a WordPress plugin on each site. The server signs every request it sends to the site, and the AI acts as a WordPress user the site owner picks, so it never gets more power than that user has.
- Tool count: 51 tools, grouped by job (content, media, design, page builders, WooCommerce, SEO, code and more).
- What it adds after a write: a snapshot of whatever the tool overwrote, taken before the write, and a fresh load of the changed page with screenshots from before and after.

MCP gives a server a way to describe a write (`readOnlyHint`, `destructiveHint`) but no way to reverse one. If your tools change things, the way back is yours to build.

## When you shouldn't build one

Check the official MCP Registry at registry.modelcontextprotocol.io and the vendor's own docs. GitHub, Stripe, Notion, Cloudflare and Webflow all host their own remote servers now. If the system you want has one, building your own means maintaining auth, uptime and tool descriptions for something you could add in a minute.

And if you run WordPress sites, you don't need to build anything either. The official [WordPress MCP Adapter](/learn/wordpress-mcp-adapter) exposes abilities your plugins register, and hosted options like Kitepod connect sites through OAuth. There's a fuller rundown of the options on the [WordPress MCP](/wordpress-mcp) page.

Build your own when the system is yours: your internal API, your database, your product. That's where nobody else can do it for you.

## What goes wrong

**The model picks the wrong tool or none at all.** Your descriptions are vague or overlap. Say when to use each tool, when not to, and describe every parameter. Ten focused tools beat one tool with a `mode` argument.

**The server works in the Inspector but not in the client.** For stdio, it's usually a relative path in the client config or a stray print to stdout. For HTTP, check the URL includes `/mcp` and the server answers over HTTPS.

**It works for you and fails for everyone else.** Your stdio server reads your credentials from your machine. A remote server needs real per-user auth, and every tool has to check the caller's permissions itself. Don't trust the model to decide who's allowed to do what.

**Writes go wrong with nothing to undo.** Mark read-only tools with `readOnlyHint: true` so clients can treat them differently, ask for confirmation on writes, and keep a copy of whatever you overwrite.

## Finer points

### What language should I build an MCP server in?

Whichever one your existing code is in. The six Tier 1 SDKs (TypeScript, Python, C#, Go, Rust, Ruby) all support tools, resources, prompts and both transports. Python has the shortest hello world.

### Do I need OAuth for a local MCP server?

No. A stdio server runs on your machine as you, so it can read an API key from an environment variable. OAuth matters once the server is remote and more than one person uses it.

### Can I build an MCP server without writing code?

Partly. On WordPress, plugins can register abilities and the MCP Adapter turns them into tools without you writing a server. But someone still has to write good tool descriptions, and that part doesn't automate well.

### How many tools should an MCP server have?

As few as cover the jobs people actually ask for. OpenAI's guide says to prefer focused tools like `list_projects` and `update_project` over one tool with many modes. Every extra tool is one more the model can confuse.

### Will my MCP server work in both Claude and ChatGPT?

A remote server works in both, since both connect to Streamable HTTP servers with OAuth. A stdio server depends on the app. The ChatGPT desktop app runs one (Settings → MCP servers → Add server, then STDIO), and it shares that setup with Codex through `~/.codex/config.toml`. ChatGPT on the web doesn't, so there a stdio server needs to sit behind a tunnel such as OpenAI's Secure MCP Tunnel.

The spec has had five dated revisions since 2024, and it'll get more. A tool with a plain name and an honest description is the part that survives all of them.
