# Build an MCP Server

The best way to understand MCP is to build a server. This guide takes you from an
empty folder to a working server with a tool and a resource, connected to a real
client. We use the official TypeScript SDK; the Python equivalent is at the end.
If you want the mental model first, read [MCP fundamentals](/guides/mcp-fundamentals)
before you start.

## How do I scaffold an MCP server project?

Install the SDK and Zod (the SDK uses it to validate tool inputs):

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

One thing to get right up front: import from the SDK's subpaths, with the `.js`
extension, even in TypeScript. These are ESM subpath exports.

```ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import * as z from "zod";
```

If you copy an import that reads `@modelcontextprotocol/server` (no `/sdk`) from
some docs, it will not resolve against the published package. Use the `/sdk/...`
paths above.

## How do I expose a tool in an MCP server?

A tool needs a name, a config (title, description, and an input schema), and an
async handler that returns content. Here is a complete, minimal server with one
tool:

```ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import * as z from "zod";

const server = new McpServer({ name: "my-server", version: "1.0.0" });

server.registerTool(
  "calculate-bmi",
  {
    title: "BMI Calculator",
    description: "Calculate Body Mass Index from weight and height.",
    inputSchema: { weightKg: z.number(), heightM: z.number() },
  },
  async ({ weightKg, heightM }) => ({
    content: [
      {
        type: "text",
        text: String(weightKg / (heightM * heightM)),
      },
    ],
  }),
);

const transport = new StdioServerTransport();
await server.connect(transport);
```

The schema is a plain object of Zod validators.Pass the raw shape (`{ weightKg: z.number() }`), not `z.object({ ... })`. Wrapping it in `z.object` is the usual slip, and the SDK then fails to derive the field list, so the model sees a tool with no parameters. The SDK turns it into the JSON
schema the model sees, and validates incoming calls against it before your
handler runs. The handler returns a `content` array; `text` is the common case,
but you can also return images or embedded resources.

## What is an MCP resource and how do I add one?

A resource is read-only context. Register one with a name, a URI, some metadata,
and a reader:

```ts
server.registerResource(
  "config",
  "config://app",
  {
    title: "Application Config",
    description: "Current application configuration.",
    mimeType: "text/plain",
  },
  async (uri) => ({
    contents: [{ uri: uri.href, text: "log_level=info\nregion=eu" }],
  }),
);
```

## How do I run my MCP server and connect it to a client?

During development, run the server straight from TypeScript:A stdio server speaks JSON-RPC over stdout, so a stray `console.log` inside a handler writes garbage into that same stream and the client drops the connection with a cryptic parse error. Send all your logging to stderr instead (`console.error`), which the protocol leaves alone.

```bash
npx tsx server.ts
```

Then connect it to Claude Code so the model can use it:

```bash
claude mcp add --transport stdio my-server -- npx tsx /absolute/path/to/server.ts
```

To inspect the server on its own, without a model in the loop, use the Inspector.
It gives you a UI to list and call your tools and resources directly, which is
the fastest way to confirm everything works before wiring it into an agent:

```bash
npx @modelcontextprotocol/inspector
```

## The Python version

Python's SDK has a high-level API called FastMCP that mirrors this closely:

```python
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("my-server")

@mcp.tool()
def calculate_bmi(weight_kg: float, height_m: float) -> float:
    """Calculate Body Mass Index."""
    return weight_kg / (height_m ** 2)

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

Install it with `pip install "mcp[cli]"`. Decorate functions with `@mcp.tool()`,
`@mcp.resource(...)`, and `@mcp.prompt()`, and run.

## How do I deploy an MCP server to production?

Once it works locally, the path to production is the transport: swap the stdio
transport for Streamable HTTP, host it, and add auth. That move from local to
networked is covered in [local vs remote MCP servers](/guides/local-vs-remote-mcp-servers).
Keep secrets in environment variables, scope what the server can touch (see
[MCP security basics](/guides/mcp-security-basics)), and you have a capability any
MCP client can connect to. To see servers other people have
already built, browse [useful MCP servers](/guides/useful-mcp-servers).
