# How to build an MCP server

> Build an MCP server with an official SDK, tool definitions, a stdio or Streamable HTTP transport and a test in the Inspector, following the spec security rules.

Source: https://www.honeyhive.ai/learn/build-an-mcp-server (updated 2026-11-11)

To build an MCP server, pick an official SDK, register tools with a name, a description and a JSON Schema for the inputs, serve them over stdio or Streamable HTTP, and test with the Inspector before you connect a client. The [MCP docs](https://modelcontextprotocol.io/docs/develop/build-server) walk through a weather server in several languages. This page covers the choices around that tutorial and the rules the spec sets for a server. For what the protocol is, see [what is the Model Context Protocol](/learn/model-context-protocol).

## Decide what the server exposes

An MCP server can offer three kinds of capability, [according to the docs](https://modelcontextprotocol.io/docs/develop/build-server): resources (file-like data a client can read), tools (functions the model can call, with user approval) and prompts (pre-written templates). The tutorial focuses on tools. If you are unsure whether you need a server at all, [MCP vs API](/learn/mcp-vs-api) compares it with a direct API call.

## Pick an SDK

The MCP docs list [official SDKs](https://modelcontextprotocol.io/docs/sdk) in tiers based on "feature completeness, protocol support, and maintenance commitment."

| Tier | SDKs |
|---|---|
| Tier 1 | TypeScript, Python, C#, Go, Rust, Ruby |
| Tier 2 | Java |
| Tier 3 | Swift, PHP, Kotlin |

Every SDK [can create](https://modelcontextprotocol.io/docs/sdk) servers that expose tools, resources and prompts over local and remote transports. The docs also [warn](https://modelcontextprotocol.io/docs/sdk) that SDKs adopt new protocol revisions on their own schedules, so check which revision your SDK supports. The [quickstarts](https://modelcontextprotocol.io/docs/develop/build-server) need Python 3.10 or higher for Python and Node.js 20 or higher for TypeScript.

## Choose a transport

The spec [defines two standard transports](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports):

| | stdio | Streamable HTTP |
|---|---|---|
| How it runs | The client launches your server as a subprocess and exchanges newline-delimited messages over its standard streams | Your server runs as its own process behind a single HTTP endpoint that accepts POST |
| Replies | Over [stdout](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio) | A JSON object, or an SSE stream scoped to that request |
| Fits | A local server a desktop client starts | A remote server that serves many clients |

The older HTTP+SSE transport from 2024-11-05 is [deprecated](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http), and new implementations should not adopt it. The 2026-07-28 revision also removed protocol-level sessions and the GET stream endpoint from Streamable HTTP, so a server that needs state across calls should return an explicit handle from a creation tool and take it as an argument later, [as the tools spec suggests](https://modelcontextprotocol.io/specification/2026-07-28/server/tools).

## Define tools

A tool definition [has](https://modelcontextprotocol.io/specification/2026-07-28/server/tools) a unique `name`, a `description` and an `inputSchema` that must be a JSON Schema object. It can also have a `title`, an `outputSchema` and `annotations`. The spec says names should be 1 to 128 characters of letters, digits, underscore, hyphen or dot, with no spaces. A server that supports tools must declare the `tools` capability, and the spec [recommends](https://modelcontextprotocol.io/specification/2026-07-28/server/tools) returning tools in a deterministic order so clients can cache the list.

The Python SDK quickstart declares a tool by decorating a function with `@mcp.tool()`, and the docstring becomes the description ([MCP docs](https://modelcontextprotocol.io/docs/develop/build-server)):

```python
@mcp.tool()
async def get_alerts(state: str) -> str:
    """Get weather alerts for a US state.
```

In TypeScript the [quickstart](https://modelcontextprotocol.io/docs/develop/build-server) calls `server.registerTool` with a name, a description, a Zod input schema and a handler.

## Keep stdout clean

On a stdio server, stdout carries the protocol. The docs [state](https://modelcontextprotocol.io/docs/develop/build-server): "Never write to stdout. Writing to stdout will corrupt the JSON-RPC messages and break your server." In Python that rules out `print()`; use the `logging` module, which writes to stderr. In TypeScript, use `console.error()` instead of `console.log()`. The docs give the same warning for Java, Kotlin, C# and Ruby.

## Return errors the model can use

The tools spec [separates](https://modelcontextprotocol.io/specification/2026-07-28/server/tools) two kinds of error. Protocol errors (an unknown tool, a malformed request) come back as JSON-RPC errors. Tool execution errors (an API failure, a bad date, a business rule) go in the result with `isError: true`, so the model can read the message and retry with different arguments. Put the actionable detail in the second kind.

## Follow the security rules

The tools spec [says](https://modelcontextprotocol.io/specification/2026-07-28/server/tools) servers must validate all tool inputs, implement access controls, rate limit tool invocations and sanitize tool outputs. It also says there should always be a human in the loop with the ability to deny tool invocations.

For Streamable HTTP, the transport spec [adds](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http) three rules:

- Validate the `Origin` header on every connection and answer 403 if it is invalid, to prevent DNS rebinding.
- When running locally, bind to 127.0.0.1 rather than 0.0.0.0.
- Authenticate all connections.

The attacks behind these rules, such as tool poisoning and token passthrough, are in [MCP security](/learn/mcp-security).

## Test before you connect a client

The [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) is the reference tool for testing servers. It needs Node 22.19.0 or newer and runs through `npx`:

```bash
npx @modelcontextprotocol/inspector node path/to/server/index.js
```

That opens a web UI against a local stdio server. A `--cli` mode lists and calls tools from a script, and `--server-url` with `--transport http` points it at a remote server. One catch from the build guide: the Inspector [defaults](https://modelcontextprotocol.io/docs/develop/build-server) to the `legacy` protocol era, so to check that a server answers 2026-07-28 clients, run it with `--protocol-era modern` and then again with `--protocol-era legacy`.

In the TypeScript SDK, start the server with `serveStdio` rather than connecting a `StdioServerTransport` by hand. The docs [say](https://modelcontextprotocol.io/docs/develop/build-server) the hand-wired version only serves 2025-era clients.

## Trace the server once it runs

A server that works in the Inspector can still fail in production on slow upstream calls or bad arguments from the model. [Tracing MCP servers](/learn/mcp-observability) covers what to record on each call. HoneyHive's docs [show how to trace MCP servers and clients](https://docs.honeyhive.ai/v2/tracing/mcp-tracing.md) with OpenInference auto-tracing or the `@trace` decorator.

## FAQ

### How do I build an MCP server?

Pick an [official SDK](https://modelcontextprotocol.io/docs/sdk), define tools with names, descriptions and input schemas, serve them over stdio or Streamable HTTP, and test with the [Inspector](https://modelcontextprotocol.io/docs/tools/inspector) before connecting a client.

### Which language should I use for an MCP server?

The MCP docs list Tier 1 SDKs for [TypeScript, Python, C#, Go, Rust and Ruby](https://modelcontextprotocol.io/docs/sdk). Java is Tier 2, and Swift, PHP and Kotlin are Tier 3.

### Should I use stdio or HTTP for my MCP server?

Use stdio when a client launches your server as a local subprocess. Use [Streamable HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http) for a server that runs elsewhere and serves many clients.

### Why does my stdio MCP server break when I add a print statement?

Output on stdout is the JSON-RPC channel. The MCP docs [say](https://modelcontextprotocol.io/docs/develop/build-server) writing to stdout corrupts the messages and breaks the server, so log to stderr instead.

### How do I test an MCP server?

Run the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) against it, for example `npx @modelcontextprotocol/inspector node path/to/server/index.js`, and list and call its tools.
