How to Build an MCP Server in Python or TypeScript
September 30, 2026

Here's how to build an MCP server in about fifteen lines: install an official SDK, write one function, mark it as a tool, and run it over stdio. The host application launches your file, the model sees your function, and it can call it.
That's the whole loop. Five steps:
- Pick an SDK. Python (
mcpon PyPI) or TypeScript (@modelcontextprotocol/serveron npm). - Define a tool. A function with typed inputs and a description the model reads.
- Serve it over stdio. The host starts your server as a child process and talks to it over stdin and stdout.
- Test it in the MCP Inspector before any AI touches it.
- Register it with a client, such as Claude Code, with one command.
One warning before you copy code from anywhere: both official SDKs shipped a v2 alongside the 2026-07-28 protocol revision, and a lot of tutorials still show v1 code. If you see FastMCP imported from mcp.server.fastmcp, or a TypeScript import from @modelcontextprotocol/sdk, you're reading the old shape. Everything below follows the current SDK READMEs as of September 2026.
Python vs TypeScript: which SDK to build with
The official quickstart covers eight languages, including Java, Kotlin, C#, Ruby, Rust, and Go. Most first servers are Python or TypeScript, so those are the two covered here.
| Python SDK | TypeScript SDK | |
|---|---|---|
| Package | mcp (add the cli extra for the dev tools) | @modelcontextprotocol/server |
| Runtime | Python 3.10+ | Node.js 20+, Bun, or Deno |
| Where the input schema comes from | Your type hints and docstring | A Zod schema you pass in |
| Local test command | uv run mcp dev server.py | npx @modelcontextprotocol/inspector plus your launch command |
| v1 name in older tutorials | FastMCP | @modelcontextprotocol/sdk |
Pick the language your tool's logic already lives in. If the server wraps a pandas script or a Python client library, use Python. If it sits next to a Node service or calls an npm package, use TypeScript. The protocol is identical either way, and a client can't tell which one you used.
Not sure you need a server at all? If the goal is one integration inside your own app, a plain API call may be simpler. Our MCP vs API breakdown covers when the protocol earns its overhead. And if you want the background on hosts, clients, and servers first, start with what MCP is.
How to build an MCP server in Python
The Python SDK needs Python 3.10 or later. The official docs use uv for project setup; plain pip install "mcp[cli]" works too.
uv init demo
cd demo
uv add "mcp[cli]"
The cli extra adds the mcp command, which gives you mcp dev, mcp run, and mcp install. You'll use all three.
Now create server.py:
from mcp.server import MCPServer
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
if __name__ == "__main__":
mcp.run()
That's a complete server. Three things are doing the work:
MCPServer("Demo")creates the server. In v2 this class replacedFastMCP, and the v2 release notes are blunt that the old import path is gone rather than deprecated.@mcp.tool()registers the function as a tool. The SDK builds the tool definition from the function itself: the name from the function name, the description from the docstring, and the input schema froma: int, b: int. You never write JSON Schema.mcp.run()with no arguments starts a stdio server. Keep it under the__main__guard, because the SDK's own commands import your file and an unguarded call would start a server on import.
Adding a resource or a prompt works the same way, with @mcp.resource() and @mcp.prompt(). Start with tools. They're what most people mean by an MCP server.
Not ready to migrate a v1 project? pip install mcp now installs 2.x. The Python maintainers suggest pinning your requirement below version 2 until you've ported, and v1.x still receives critical fixes and security patches.
How to build a TypeScript MCP server
The TypeScript SDK v2 split into separate packages: @modelcontextprotocol/server for servers and @modelcontextprotocol/client for clients. Schemas use Standard Schema, so Zod v4 is the common choice.
mkdir greeting
cd greeting
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod tsx
type=module matters because the SDK ships ES modules only. tsx runs TypeScript directly, so there's no build step while you're developing.
Create src/index.ts. This is the minimal example from the SDK README:
import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
const server = new McpServer({ name: 'greeting-server', version: '1.0.0' });
server.registerTool(
'greet',
{
description: 'Greet someone by name',
inputSchema: z.object({ name: z.string() })
},
async ({ name }) => ({
content: [{ type: 'text', text: `Hello, ${name}!` }]
})
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main();
registerTool takes a name, a config, and a handler. The Zod schema is the only schema you write: the SDK turns it into the JSON Schema the model sees and validates every call against it before your handler runs. The handler returns content, a list of typed blocks.
Start it with npx tsx src/index.ts. Nothing visible happens, and that's correct. A stdio server sits waiting for a client to open the conversation.
Test your server with the MCP Inspector
Don't debug through a chatbot. The MCP Inspector is the official tool for calling a server's tools directly: it launches your server, connects over stdio, and shows you exactly what a model would receive.
| Language | Command |
|---|---|
| Python | uv run mcp dev server.py |
| TypeScript | npx @modelcontextprotocol/inspector npx tsx src/index.ts |
| Any stdio server | npx @modelcontextprotocol/inspector followed by your launch command |
Open the URL it prints, click Connect, open the Tools tab, and run your tool. Call add with 1 and 2 and you should get 3 back.
Two things are worth checking in the Inspector, not just "does it run":
- Read your tool's description as the model will. The name, description, and input schema are what the model uses to decide whether to call your tool. A vague docstring like "Does stuff with data" is why a model ignores a perfectly working tool.
- Send it bad input. The TypeScript SDK rejects arguments that fail the schema before your handler runs, and the model reads that error. Make sure the message is one it can act on.
Connect your MCP server to Claude Code and other clients
Connecting a local server is always the same act: you tell the host the command that starts your server. Only where you put that command changes.
Claude Code takes it on the command line. Everything after the -- is the launch command, passed to your server untouched:
# Python
claude mcp add demo -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
# TypeScript, from the project root
claude mcp add greeting -- npx tsx src/index.ts
Then run /mcp inside a Claude Code session to confirm the server is connected and its tools are listed. Scopes, config files, and why a server sometimes loads the wrong definition are covered in our Claude Code MCP configuration guide.
Other hosts take the same command in a JSON file:
| Host | Where the command goes |
|---|---|
| Claude Code | claude mcp add, no file to edit |
| Claude Desktop | claude_desktop_config.json, or run uv run mcp install server.py and the Python SDK writes the entry for you |
| Cursor | .cursor/mcp.json in the project root, under mcpServers |
| VS Code | .vscode/mcp.json in the project root, under servers |
For Claude Desktop, fully quit the app and reopen it after any config change. Closing the window doesn't reload servers.
Would rather not scaffold by hand? Anthropic publishes an official mcp-server-dev plugin for Claude Code. Install it, run its build skill, and Claude asks about your use case and scaffolds either a remote HTTP or a local stdio server.
Five mistakes that break a first MCP server
Almost every "it runs but the host can't see it" report is one of these.
| Symptom | Cause | Fix |
|---|---|---|
| Server connects, then fails with garbled messages | You printed to stdout. In a stdio server, stdout is the protocol channel | Log to stderr: Python's logging module, or console.error in Node |
ImportError on mcp.server.fastmcp | v1 tutorial code on the v2 SDK | Import MCPServer from mcp.server, or pin mcp below 2 |
| Host can't find the server file | Relative path in the config | Use an absolute path. The host launches from its own working directory |
Host can't find uv or npx | Hosts spawn servers with a minimal PATH | Replace the bare command with its absolute path (which uv) |
| Your API key is missing inside the server | The host doesn't inherit your shell's environment | Pass it explicitly, for example --env on claude mcp add |
The first row is the one that costs people an afternoon. One stray print() or console.log() corrupts the JSON-RPC stream, and the error you see rarely points back to it.
Local stdio vs a remote MCP server
Everything above runs your server on the same machine as the host. That's the right shape for a personal tool. It stops being enough when other people need the server and don't have your file.
| Local stdio | Remote (Streamable HTTP) | |
|---|---|---|
| Who runs it | The host launches it as a child process | You host it; clients connect by URL |
| What you hand out | A command | A URL |
| Best for | Personal tools, local files, scripts | Shared tools, teams, public integrations |
| Extra work | Almost none | Hosting, and authentication if it touches private data |
The same server object can serve both. The Python SDK serves it over HTTP with uv run mcp run server.py --transport streamable-http, and the TypeScript SDK ships Streamable HTTP transports plus optional middleware for Express, Fastify, and Hono.
Authentication is its own project, and not one to improvise. Claude Code's docs point server builders to Anthropic's connector building guide for authentication, testing, and Directory submission, and each SDK's docs cover its OAuth helpers. Build and test the local version first. When you want to see how production servers are laid out, read the source of the reference servers in the official MCP repositories.
If you'd rather run a setup than build one
Building a server makes sense when the tool you need doesn't exist yet. Often it does: someone already wired the integration, got the prompts right, and made it work. What's missing is a way to use their working setup without rebuilding it.
That's the gap Taku sits in. It's an AI-native desktop workspace where you mirror an AI app, skill, or workflow someone already got working, run it, remix it for your own files, and keep it. It won't write your server for you, and the SDKs above are the right tools when you're the one building. But if your real goal was the result rather than the server, the free app library is a shorter road. Taku is in Beta, and the Mac app is available now.
FAQ
What do I need to build an MCP server?
One of the official SDKs and a language runtime: Python 3.10+ for the Python SDK, or Node.js 20+ (Bun and Deno also work) for the TypeScript SDK. For testing, the MCP Inspector. You don't need an API key for any model, because the server never talks to the model directly. The host does.
Is FastMCP still the way to create an MCP server in Python?
Not in the official SDK. In v2 the high-level class was renamed to MCPServer and moved to mcp.server. The decorators work the same way, so porting a simple v1 server is mostly a two-line change. The SDK's migration guide lists the other renames.
How do I test an MCP server without Claude?
Use the MCP Inspector. uv run mcp dev server.py opens it for a Python server, and npx @modelcontextprotocol/inspector followed by your launch command works for any stdio server. You can call each tool directly and see the exact result a model would get.
Why does my MCP server work in the Inspector but not in Claude Desktop?
Usually a relative path, a command that isn't on the host's PATH, or a config change the app never reloaded. Use absolute paths, the absolute path to uv or npx, and fully quit the app before reopening it.
Should I build a local or a remote MCP server?
Start local. A stdio server is the fastest way to prove your tools work. Move to Streamable HTTP when other people need to connect without having your code, and plan for authentication at that point.