How to Build an MCP Server (TypeScript and Python)
Build a working Model Context Protocol server that gives Claude Code and other AI tools new abilities. Tools, resources and prompts explained, a TypeScript server with the official SDK and a Python one with FastMCP, connecting it to Claude Code, stdio vs HTTP transports, and designing tools agents use well.
An MCP server exposes abilities — tools, data, prompt templates — that any MCP-compatible AI client can use: Claude Code, Claude Desktop, Cursor, VS Code and others. Write it once, and every client can call it. (What is MCP?, MCP vs API)
You'd build one to let an agent query your internal API, look things up in your database safely, trigger a deploy, or read your company's documentation.
The three things a server can offer
- Tools — functions the model can call:
search_orders,create_ticket. The most common by far. - Resources — data the client can read, identified by URI: a file, a record, a report.
- Prompts — reusable prompt templates the user can invoke.
Start with tools.
A TypeScript server
npm init -y
npm install @modelcontextprotocol/sdk zod
// server.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'
const server = new McpServer({ name: 'orders', version: '1.0.0' })
server.registerTool(
'get_order',
{
title: 'Get order',
description: 'Look up an order by its ID. Returns status, total and line items.',
inputSchema: { orderId: z.string().describe('Order ID, e.g. ord_123') },
},
async ({ orderId }) => {
const res = await fetch(`${process.env.ORDERS_API}/orders/${orderId}`, {
headers: { Authorization: `Bearer ${process.env.ORDERS_TOKEN}` },
})
if (!res.ok) {
return { content: [{ type: 'text', text: `Order ${orderId} not found (HTTP ${res.status})` }], isError: true }
}
return { content: [{ type: 'text', text: JSON.stringify(await res.json(), null, 2) }] }
},
)
await server.connect(new StdioServerTransport())
Note: with the stdio transport, never console.log — stdout is the protocol channel. Log to stderr (console.error).
The same in Python (FastMCP)
uv add "mcp[cli]" httpx
# server.py
import os, httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("orders")
@mcp.tool()
async def get_order(order_id: str) -> str:
"""Look up an order by its ID. Returns status, total and line items."""
async with httpx.AsyncClient() as client:
r = await client.get(
f"{os.environ['ORDERS_API']}/orders/{order_id}",
headers={"Authorization": f"Bearer {os.environ['ORDERS_TOKEN']}"},
)
r.raise_for_status()
return r.text
if __name__ == "__main__":
mcp.run()
The docstring and type hints become the tool's description and schema.
Connect it to Claude Code
claude mcp add orders -e ORDERS_API=https://api.internal -e ORDERS_TOKEN=$ORDERS_TOKEN -- npx tsx server.ts
Check with /mcp, then ask: "What's the status of order ord_123?" (Connecting MCP servers to Claude Code)
The MCP Inspector (npx @modelcontextprotocol/inspector) lets you call your tools by hand while developing.
stdio vs HTTP
| stdio | Streamable HTTP | |
|---|---|---|
| Runs | As a local process the client starts | As a web service |
| Good for | Personal tools, local files, dev | Shared servers, teams, remote clients |
| Auth | Environment variables | OAuth or tokens |
Start local with stdio. Move to HTTP when several people or machines need the same server — then add authentication, and treat it like any public API. (API authentication methods)
Designing tools agents use well
The model chooses tools from their names and descriptions. Good design matters more than code:
- Describe when to use it, not just what it does. Include formats and examples in parameter descriptions.
- Fewer, higher-level tools beat many tiny ones.
get_customer_summaryis better than six calls the model must chain. - Return what the model needs, concisely. Huge JSON dumps waste context; paginate and trim. (Context engineering)
- Helpful errors (
isError: truewith a reason and a hint) let the model recover. - Separate read and write tools, so clients can allow reads freely and gate writes.
Security
Your server runs with whatever credentials you give it, on behalf of a model that may be reading untrusted text:
- Least privilege — a read-only token unless writes are essential. (Principle of least privilege)
- Validate inputs server-side; never pass model input straight into SQL or shell commands. (SQL injection)
- Confirm destructive actions — make them separate tools that clients will prompt for.
- Don't leak secrets in tool output.
More in Securing MCP servers.
Distributing it
Package it as a Claude Code plugin to share with your team alongside skills and hooks. (Claude Code plugins)
EasySpawn gives you a persistent server to run HTTP MCP servers next to the APIs and databases they wrap — always on, behind HTTPS, reachable by your whole team's agents. See how it works or join the waitlist.
Related: What Is MCP? · Securing MCP Servers · Connecting MCP Servers to Claude Code · What Is Function Calling?
Keep reading
Test-Driven Development With Claude Code
Tests first is the single best way to make an AI coding agent reliable: it gives Claude a pass/fail signal to iterate against. A red-green-refactor workflow for Claude Code, prompts that stop it cheating the tests, enforcing it with hooks and /goal, and where TDD with agents falls short.
Claude Code Plugins: Install, Manage and Build Your Own
A Claude Code plugin bundles skills, subagents, hooks and MCP servers into one installable unit, distributed through marketplaces. How to install plugins and choose a scope, what an enabled plugin costs you in context, the official marketplace, sharing a team setup, and building your own with plugin.json.